diff --git a/packages/moxxmpp/lib/src/negotiators/namespaces.dart b/packages/moxxmpp/lib/src/negotiators/namespaces.dart index a62dd44..17691ec 100644 --- a/packages/moxxmpp/lib/src/negotiators/namespaces.dart +++ b/packages/moxxmpp/lib/src/negotiators/namespaces.dart @@ -10,3 +10,4 @@ const startTlsNegotiator = 'im.moxxmpp.core.starttls'; const sasl2Negotiator = 'org.moxxmpp.sasl.sasl2'; const bind2Negotiator = 'org.moxxmpp.bind2'; const saslFASTNegotiator = 'org.moxxmpp.sasl.fast'; +const carbonsNegotiator = 'org.moxxmpp.bind2.carbons'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0280.dart b/packages/moxxmpp/lib/src/xeps/xep_0280.dart index 5a2ad78..f3efc24 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0280.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0280.dart @@ -1,3 +1,4 @@ +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:moxxmpp/src/connection.dart'; import 'package:moxxmpp/src/events.dart'; @@ -7,10 +8,15 @@ import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/handlers.dart'; import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; +import 'package:moxxmpp/src/negotiators/namespaces.dart'; +import 'package:moxxmpp/src/negotiators/negotiator.dart'; +import 'package:moxxmpp/src/negotiators/sasl2.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; +import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0297.dart'; +import 'package:moxxmpp/src/xeps/xep_0386.dart'; /// This manager class implements support for XEP-0280. class CarbonsManager extends XmppManagerBase { @@ -173,6 +179,16 @@ class CarbonsManager extends XmppManagerBase { _isEnabled = true; } + @internal + void setEnabled() { + _isEnabled = true; + } + + @internal + void setDisabled() { + _isEnabled = false; + } + /// Checks if a carbon sent by [senderJid] is valid to prevent vulnerabilities like /// the ones listed at https://xmpp.org/extensions/xep-0280.html#security. /// @@ -185,3 +201,82 @@ class CarbonsManager extends XmppManagerBase { ); } } + +class CarbonsNegotiator extends Sasl2FeatureNegotiator + implements Bind2FeatureNegotiator { + CarbonsNegotiator() : super(0, false, carbonsXmlns, carbonsNegotiator); + + /// Flag indicating whether we requested to enable carbons inline (true) or not + /// (false). + bool _requestedEnablement = false; + + /// Logger + final Logger _log = Logger('CarbonsNegotiator'); + + @override + bool canInlineFeature(List features) => true; + + @override + Future> onSasl2FeaturesReceived(XMLNode sasl2Features) async { + return []; + } + + @override + Future> onSasl2Success(XMLNode response) async { + if (_requestedEnablement) { + final enabled = response + .firstTag('bound', xmlns: bind2Xmlns) + ?.firstTag('enabled', xmlns: carbonsXmlns); + final cm = attributes.getManagerById(carbonsManager)!; + if (enabled != null) { + _log.finest('Successfully enabled Message Carbons inline'); + cm.setEnabled(); + } else { + _log.warning('Failed to enable Message Carbons inline'); + cm.setDisabled(); + } + } + + return const Result(true); + } + + @override + Future> negotiate( + XMLNode nonza, + ) async { + return const Result(NegotiatorState.done); + } + + @override + Future> onBind2FeaturesReceived( + List bind2Features) async { + if (!bind2Features.contains(carbonsXmlns)) { + return []; + } + + _requestedEnablement = true; + return [ + XMLNode.xmlns( + tag: 'enable', + xmlns: carbonsXmlns, + ), + ]; + } + + @override + Future postRegisterCallback() async { + attributes + .getNegotiatorById(sasl2Negotiator) + ?.registerNegotiator(this); + attributes + .getNegotiatorById(bind2Negotiator) + ?.registerNegotiator(this); + } + + @override + void reset() { + _requestedEnablement = false; + + super.reset(); + } +} diff --git a/packages/moxxmpp/test/xeps/xep_0280_test.dart b/packages/moxxmpp/test/xeps/xep_0280_test.dart index 282317e..c29c751 100644 --- a/packages/moxxmpp/test/xeps/xep_0280_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0280_test.dart @@ -1,8 +1,11 @@ import 'package:moxxmpp/moxxmpp.dart'; import 'package:test/test.dart'; +import '../helpers/logging.dart'; import '../helpers/xmpp.dart'; void main() { + initLogger(); + test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", () async { final attributes = XmppManagerAttributes( @@ -52,4 +55,92 @@ void main() { false, ); }); + + test('Test enabling message carbons inline with Bind2', () async { + final fakeSocket = StubTCPSocket([ + StringExpectation( + "", + ''' + + + + PLAIN + + + PLAIN + + + + + + + + + + + + + + ''', + ), + StanzaExpectation( + "moxxmppPapaTutuWawa's awesome deviceAHBvbHlub21kaXZpc2lvbgBhYWFh", + ''' + + polynomdivision@test.server/test-resource + + + + + ''', + ), + ]); + final conn = XmppConnection( + TestingReconnectionPolicy(), + AlwaysConnectedConnectivityManager(), + fakeSocket, + ) + ..setConnectionSettings( + ConnectionSettings( + jid: JID.fromString('polynomdivision@test.server'), + password: 'aaaa', + useDirectTLS: true, + ), + ) + ..setResource('test-resource', triggerEvent: false); + await conn.registerManagers([ + RosterManager(TestingRosterStateManager('', [])), + DiscoManager([]), + CarbonsManager(), + ]); + + await conn.registerFeatureNegotiators([ + SaslPlainNegotiator(), + ResourceBindingNegotiator(), + CarbonsNegotiator(), + Bind2Negotiator(), + Sasl2Negotiator( + userAgent: const UserAgent( + id: 'd4565fa7-4d72-4749-b3d3-740edbf87770', + software: 'moxxmpp', + device: "PapaTutuWawa's awesome device", + ), + ), + ]); + + final result = await conn.connect( + waitUntilLogin: true, + shouldReconnect: false, + enableReconnectOnSuccess: false, + ); + expect(result.isType(), false); + expect(conn.resource, 'test-resource'); + expect( + conn.getManagerById(carbonsManager)!.isEnabled, true); + }); }