aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dino.doap16
-rw-r--r--dino.doap.in16
-rw-r--r--libdino/src/service/connection_manager.vala5
-rw-r--r--libdino/src/service/module_manager.vala11
-rw-r--r--xmpp-vala/meson.build2
-rw-r--r--xmpp-vala/src/core/direct_tls_xmpp_stream.vala4
-rw-r--r--xmpp-vala/src/core/io_xmpp_stream.vala17
-rw-r--r--xmpp-vala/src/core/starttls_xmpp_stream.vala4
-rw-r--r--xmpp-vala/src/core/stream_connect.vala4
-rw-r--r--xmpp-vala/src/core/tls_xmpp_stream.vala12
-rw-r--r--xmpp-vala/src/core/xmpp_stream.vala5
-rw-r--r--xmpp-vala/src/module/sasl.vala179
-rw-r--r--xmpp-vala/src/module/xep/0198_stream_management.vala157
-rw-r--r--xmpp-vala/src/module/xep/0280_message_carbons.vala30
-rw-r--r--xmpp-vala/src/module/xep/0386_bind2.vala49
-rw-r--r--xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala139
16 files changed, 503 insertions, 147 deletions
diff --git a/dino.doap b/dino.doap
index 96e6913a..f069775a 100644
--- a/dino.doap
+++ b/dino.doap
@@ -634,6 +634,22 @@
</implements>
<implements>
<xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0386.html" />
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1.0</xmpp:version>
+ <xmpp:since>0.6</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0388.html" />
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.0.4</xmpp:version>
+ <xmpp:since>0.6</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+ <implements>
+ <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>0.1.2</xmpp:version>
diff --git a/dino.doap.in b/dino.doap.in
index 99e3565d..7716efc5 100644
--- a/dino.doap.in
+++ b/dino.doap.in
@@ -454,6 +454,22 @@
</implements>
<implements>
<xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0386.html" />
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1.0</xmpp:version>
+ <xmpp:since>0.6</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0388.html" />
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.0.4</xmpp:version>
+ <xmpp:since>0.6</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
+ <implements>
+ <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html" />
<xmpp:status>partial</xmpp:status>
<xmpp:version>0.1.2</xmpp:version>
diff --git a/libdino/src/service/connection_manager.vala b/libdino/src/service/connection_manager.vala
index c8b5c4ed..5e92dec9 100644
--- a/libdino/src/service/connection_manager.vala
+++ b/libdino/src/service/connection_manager.vala
@@ -231,12 +231,13 @@ public class ConnectionManager : Object {
stream.attached_modules.connect((stream) => {
stream_attached_modules(account, stream);
change_connection_state(account, ConnectionState.CONNECTED);
-
-// stream.get_module(Xep.Muji.Module.IDENTITY).join_call(stream, new Jid("test@muc.poez.io"), true);
});
stream.get_module(Sasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => {
set_connection_error(account, new ConnectionError(ConnectionError.Source.SASL, null));
});
+ stream.get_module(Xep.ExtensibleSaslProfile.Module.IDENTITY).received_auth_failure.connect((stream, node) => {
+ set_connection_error(account, new ConnectionError(ConnectionError.Source.SASL, null));
+ });
string connection_uuid = connections[account].uuid;
stream.received_node.connect(() => {
diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala
index 64be76e1..127f2d93 100644
--- a/libdino/src/service/module_manager.vala
+++ b/libdino/src/service/module_manager.vala
@@ -46,8 +46,19 @@ public class ModuleManager {
lock(module_map) {
module_map[account] = new ArrayList<XmppStreamModule>();
module_map[account].add(new Iq.Module());
+
+ var sasl2_module = new Xep.ExtensibleSaslProfile.Module(account.bare_jid.localpart, account.password);
+ module_map[account].add(sasl2_module);
module_map[account].add(new Sasl.Module(account.bare_jid.to_string(), account.password));
module_map[account].add(new Xep.StreamManagement.Module());
+
+ var bind2_sasl2_activation = new Xep.Bind2.Sasl2Activation();
+ bind2_sasl2_activation.inline_activation_providers[Xep.MessageCarbons.NS_URI] = new Xep.MessageCarbons.Bind2Activation();
+ bind2_sasl2_activation.inline_activation_providers[Xep.StreamManagement.NS_URI] = new Xep.StreamManagement.Bind2Activation();
+
+ sasl2_module.inline_activation_providers[Xep.Bind2.NS_URI] = bind2_sasl2_activation;
+ sasl2_module.inline_activation_providers[Xep.StreamManagement.NS_URI] = new Xep.StreamManagement.Sasl2Activation();
+
module_map[account].add(new Bind.Module(account.resourcepart));
module_map[account].add(new Session.Module());
module_map[account].add(new Roster.Module());
diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build
index b035e367..940b9877 100644
--- a/xmpp-vala/meson.build
+++ b/xmpp-vala/meson.build
@@ -117,6 +117,8 @@ sources = files(
'src/module/xep/0363_http_file_upload.vala',
'src/module/xep/0367_message_attaching.vala',
'src/module/xep/0380_explicit_encryption.vala',
+ 'src/module/xep/0386_bind2.vala',
+ 'src/module/xep/0388_extensible_sasl_profile.vala',
'src/module/xep/0384_omemo/omemo_decryptor.vala',
'src/module/xep/0384_omemo/omemo_encryptor.vala',
'src/module/xep/0391_jingle_encrypted_transports.vala',
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);
+ }
+}