2014-02-28 17:46:01 +00:00
package eu.siacs.conversations.xmpp ;
2014-01-30 15:42:35 +00:00
2022-09-06 07:25:09 +00:00
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM ;
2018-03-15 07:53:19 +00:00
import android.content.Context ;
2015-10-11 11:11:50 +00:00
import android.graphics.Bitmap ;
import android.graphics.BitmapFactory ;
2022-09-15 12:28:51 +00:00
import android.os.Build ;
2014-11-03 09:03:45 +00:00
import android.os.SystemClock ;
2015-10-11 18:45:01 +00:00
import android.security.KeyChain ;
2015-10-11 11:11:50 +00:00
import android.util.Base64 ;
2014-11-03 09:03:45 +00:00
import android.util.Log ;
2014-12-30 13:50:51 +00:00
import android.util.Pair ;
2014-11-03 09:03:45 +00:00
import android.util.SparseArray ;
2021-01-23 08:25:34 +00:00
import androidx.annotation.NonNull ;
2022-10-15 18:53:59 +00:00
import androidx.annotation.Nullable ;
2021-01-23 08:25:34 +00:00
2022-12-30 09:53:49 +00:00
import com.google.common.base.Optional ;
2022-06-14 06:39:55 +00:00
import com.google.common.base.Strings ;
2014-11-03 09:03:45 +00:00
import org.xmlpull.v1.XmlPullParserException ;
2015-10-11 11:11:50 +00:00
import java.io.ByteArrayInputStream ;
2014-01-30 15:42:35 +00:00
import java.io.IOException ;
import java.io.InputStream ;
2014-11-18 00:48:16 +00:00
import java.net.ConnectException ;
2017-07-10 06:50:01 +00:00
import java.net.IDN ;
2014-11-16 11:00:53 +00:00
import java.net.InetAddress ;
2014-10-27 20:48:25 +00:00
import java.net.InetSocketAddress ;
2014-01-30 15:42:35 +00:00
import java.net.Socket ;
2017-01-12 20:26:58 +00:00
import java.net.UnknownHostException ;
2014-03-06 19:03:35 +00:00
import java.security.KeyManagementException ;
import java.security.NoSuchAlgorithmException ;
2015-10-11 18:45:01 +00:00
import java.security.Principal ;
import java.security.PrivateKey ;
import java.security.cert.X509Certificate ;
2014-03-15 03:59:18 +00:00
import java.util.ArrayList ;
2016-07-25 13:57:47 +00:00
import java.util.Arrays ;
2022-09-03 18:17:29 +00:00
import java.util.Collection ;
2018-07-07 09:20:39 +00:00
import java.util.Collections ;
2014-04-08 21:15:55 +00:00
import java.util.HashMap ;
2017-06-30 06:45:16 +00:00
import java.util.HashSet ;
2014-02-01 00:25:56 +00:00
import java.util.Hashtable ;
2015-08-06 12:54:37 +00:00
import java.util.Iterator ;
2014-02-09 13:10:52 +00:00
import java.util.List ;
2014-04-08 21:15:55 +00:00
import java.util.Map.Entry ;
2018-03-18 09:30:15 +00:00
import java.util.Set ;
2018-01-20 20:57:09 +00:00
import java.util.concurrent.CountDownLatch ;
import java.util.concurrent.TimeUnit ;
2016-04-04 18:06:07 +00:00
import java.util.concurrent.atomic.AtomicBoolean ;
import java.util.concurrent.atomic.AtomicInteger ;
2017-06-19 18:02:41 +00:00
import java.util.regex.Matcher ;
2014-01-30 15:42:35 +00:00
2015-10-11 18:45:01 +00:00
import javax.net.ssl.KeyManager ;
2014-03-06 19:03:35 +00:00
import javax.net.ssl.SSLContext ;
2021-05-03 06:28:03 +00:00
import javax.net.ssl.SSLPeerUnverifiedException ;
2014-01-30 15:42:35 +00:00
import javax.net.ssl.SSLSocket ;
import javax.net.ssl.SSLSocketFactory ;
2015-10-11 18:45:01 +00:00
import javax.net.ssl.X509KeyManager ;
2014-03-06 19:03:35 +00:00
import javax.net.ssl.X509TrustManager ;
2014-01-30 15:42:35 +00:00
2014-08-31 14:28:21 +00:00
import eu.siacs.conversations.Config ;
2018-02-27 19:33:21 +00:00
import eu.siacs.conversations.R ;
2015-10-15 15:08:38 +00:00
import eu.siacs.conversations.crypto.XmppDomainVerifier ;
2017-12-18 12:47:53 +00:00
import eu.siacs.conversations.crypto.axolotl.AxolotlService ;
2022-09-06 12:53:12 +00:00
import eu.siacs.conversations.crypto.sasl.ChannelBinding ;
2023-10-21 12:22:38 +00:00
import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism ;
2022-10-14 22:09:29 +00:00
import eu.siacs.conversations.crypto.sasl.HashedToken ;
2014-11-12 15:15:38 +00:00
import eu.siacs.conversations.crypto.sasl.SaslMechanism ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.entities.Account ;
2015-08-19 11:00:52 +00:00
import eu.siacs.conversations.entities.Message ;
2016-01-10 19:56:55 +00:00
import eu.siacs.conversations.entities.ServiceDiscoveryResult ;
2014-12-21 20:43:58 +00:00
import eu.siacs.conversations.generator.IqGenerator ;
2021-03-22 09:39:53 +00:00
import eu.siacs.conversations.http.HttpConnectionManager ;
2017-08-23 10:33:40 +00:00
import eu.siacs.conversations.persistance.FileBackend ;
2018-02-16 17:58:57 +00:00
import eu.siacs.conversations.services.MemorizingTrustManager ;
2018-07-07 09:20:39 +00:00
import eu.siacs.conversations.services.MessageArchiveService ;
2017-05-19 11:30:57 +00:00
import eu.siacs.conversations.services.NotificationService ;
2014-06-20 15:30:19 +00:00
import eu.siacs.conversations.services.XmppConnectionService ;
2023-01-06 11:41:26 +00:00
import eu.siacs.conversations.utils.AccountUtils ;
2017-07-30 09:39:47 +00:00
import eu.siacs.conversations.utils.CryptoHelper ;
2017-06-19 18:02:41 +00:00
import eu.siacs.conversations.utils.Patterns ;
2022-09-15 12:28:51 +00:00
import eu.siacs.conversations.utils.PhoneHelper ;
2017-06-21 21:28:01 +00:00
import eu.siacs.conversations.utils.Resolver ;
2022-10-14 18:00:36 +00:00
import eu.siacs.conversations.utils.SSLSockets ;
2015-11-29 15:31:51 +00:00
import eu.siacs.conversations.utils.SocksSocketFactory ;
2019-02-09 19:21:29 +00:00
import eu.siacs.conversations.utils.XmlHelper ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xml.Element ;
2019-09-12 08:12:47 +00:00
import eu.siacs.conversations.xml.LocalizedContent ;
2018-09-26 12:39:04 +00:00
import eu.siacs.conversations.xml.Namespace ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xml.Tag ;
import eu.siacs.conversations.xml.TagWriter ;
import eu.siacs.conversations.xml.XmlReader ;
2022-09-25 12:13:04 +00:00
import eu.siacs.conversations.xmpp.bind.Bind2 ;
2015-10-11 11:11:50 +00:00
import eu.siacs.conversations.xmpp.forms.Data ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived ;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket ;
2015-08-13 16:25:10 +00:00
import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza ;
2014-03-10 18:22:13 +00:00
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza ;
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket ;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket ;
2014-08-26 15:43:44 +00:00
import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket ;
import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket ;
2014-03-10 18:22:13 +00:00
import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket ;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket ;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket ;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket ;
2021-03-22 09:12:53 +00:00
import okhttp3.HttpUrl ;
2014-01-30 15:42:35 +00:00
public class XmppConnection implements Runnable {
2018-09-26 12:39:04 +00:00
private static final int PACKET_IQ = 0 ;
private static final int PACKET_MESSAGE = 1 ;
private static final int PACKET_PRESENCE = 2 ;
2022-09-03 10:16:06 +00:00
public final OnIqPacketReceived registrationResponseListener =
( account , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
account . setOption ( Account . OPTION_REGISTER , false ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : successfully registered new account on server " ) ;
throw new StateChangingError ( Account . State . REGISTRATION_SUCCESSFUL ) ;
} else {
final List < String > PASSWORD_TOO_WEAK_MSGS =
Arrays . asList (
" The password is too weak " , " Please use a longer password. " ) ;
Element error = packet . findChild ( " error " ) ;
Account . State state = Account . State . REGISTRATION_FAILED ;
if ( error ! = null ) {
if ( error . hasChild ( " conflict " ) ) {
state = Account . State . REGISTRATION_CONFLICT ;
} else if ( error . hasChild ( " resource-constraint " )
& & " wait " . equals ( error . getAttribute ( " type " ) ) ) {
state = Account . State . REGISTRATION_PLEASE_WAIT ;
} else if ( error . hasChild ( " not-acceptable " )
& & PASSWORD_TOO_WEAK_MSGS . contains (
error . findChildContent ( " text " ) ) ) {
state = Account . State . REGISTRATION_PASSWORD_TOO_WEAK ;
}
}
throw new StateChangingError ( state ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
} ;
2018-09-26 12:39:04 +00:00
protected final Account account ;
private final Features features = new Features ( this ) ;
private final HashMap < Jid , ServiceDiscoveryResult > disco = new HashMap < > ( ) ;
2020-12-01 19:31:30 +00:00
private final HashMap < String , Jid > commands = new HashMap < > ( ) ;
2018-09-26 12:39:04 +00:00
private final SparseArray < AbstractAcknowledgeableStanza > mStanzaQueue = new SparseArray < > ( ) ;
2022-09-03 10:16:06 +00:00
private final Hashtable < String , Pair < IqPacket , OnIqPacketReceived > > packetCallbacks =
new Hashtable < > ( ) ;
private final Set < OnAdvancedStreamFeaturesLoaded > advancedStreamFeaturesLoadedListeners =
new HashSet < > ( ) ;
2018-09-26 12:39:04 +00:00
private final XmppConnectionService mXmppConnectionService ;
private Socket socket ;
private XmlReader tagReader ;
private TagWriter tagWriter = new TagWriter ( ) ;
private boolean shouldAuthenticate = true ;
private boolean inSmacksSession = false ;
2022-09-25 12:13:04 +00:00
private boolean quickStartInProgress = false ;
2018-09-26 12:39:04 +00:00
private boolean isBound = false ;
private Element streamFeatures ;
2023-05-02 13:35:27 +00:00
private Element boundStreamFeatures ;
2018-09-26 12:39:04 +00:00
private String streamId = null ;
private int stanzasReceived = 0 ;
private int stanzasSent = 0 ;
2022-12-30 11:09:16 +00:00
private int stanzasSentBeforeAuthentication ;
2018-09-26 12:39:04 +00:00
private long lastPacketReceived = 0 ;
private long lastPingSent = 0 ;
private long lastConnect = 0 ;
private long lastSessionStarted = 0 ;
private long lastDiscoStarted = 0 ;
2018-12-05 18:11:40 +00:00
private boolean isMamPreferenceAlways = false ;
2020-12-31 09:27:06 +00:00
private final AtomicInteger mPendingServiceDiscoveries = new AtomicInteger ( 0 ) ;
private final AtomicBoolean mWaitForDisco = new AtomicBoolean ( true ) ;
private final AtomicBoolean mWaitingForSmCatchup = new AtomicBoolean ( false ) ;
private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger ( 0 ) ;
2018-09-26 12:39:04 +00:00
private boolean mInteractive = false ;
private int attempt = 0 ;
private OnPresencePacketReceived presenceListener = null ;
private OnJinglePacketReceived jingleListener = null ;
private OnIqPacketReceived unregisteredIqListener = null ;
private OnMessagePacketReceived messageListener = null ;
private OnStatusChanged statusListener = null ;
private OnBindListener bindListener = null ;
private OnMessageAcknowledged acknowledgedListener = null ;
private SaslMechanism saslMechanism ;
2022-10-15 10:27:38 +00:00
private HashedToken . Mechanism hashTokenRequest ;
2021-03-22 09:12:53 +00:00
private HttpUrl redirectionUrl = null ;
2018-09-26 12:39:04 +00:00
private String verifiedHostname = null ;
2023-10-13 06:29:23 +00:00
private Resolver . Result currentResolverResult ;
private Resolver . Result seeOtherHostResolverResult ;
2018-09-26 12:39:04 +00:00
private volatile Thread mThread ;
private CountDownLatch mStreamCountDownLatch ;
public XmppConnection ( final Account account , final XmppConnectionService service ) {
this . account = account ;
this . mXmppConnectionService = service ;
}
private static void fixResource ( Context context , Account account ) {
String resource = account . getResource ( ) ;
2022-09-03 10:16:06 +00:00
int fixedPartLength =
context . getString ( R . string . app_name ) . length ( ) + 1 ; // include the trailing dot
2018-09-26 12:39:04 +00:00
int randomPartLength = 4 ; // 3 bytes
if ( resource ! = null & & resource . length ( ) > fixedPartLength + randomPartLength ) {
2022-09-03 10:16:06 +00:00
if ( validBase64 (
resource . substring ( fixedPartLength , fixedPartLength + randomPartLength ) ) ) {
2018-09-26 12:39:04 +00:00
account . setResource ( resource . substring ( 0 , fixedPartLength + randomPartLength ) ) ;
}
}
}
private static boolean validBase64 ( String input ) {
try {
return Base64 . decode ( input , Base64 . URL_SAFE ) . length = = 3 ;
} catch ( Throwable throwable ) {
return false ;
}
}
2018-09-27 07:59:05 +00:00
private void changeStatus ( final Account . State nextStatus ) {
2018-09-26 12:39:04 +00:00
synchronized ( this ) {
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : not changing status to "
+ nextStatus
+ " because thread was interrupted " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
if ( account . getStatus ( ) ! = nextStatus ) {
2023-10-18 12:02:10 +00:00
if ( nextStatus = = Account . State . OFFLINE
& & account . getStatus ( ) ! = Account . State . CONNECTING
& & account . getStatus ( ) ! = Account . State . ONLINE
& & account . getStatus ( ) ! = Account . State . DISABLED
& & account . getStatus ( ) ! = Account . State . LOGGED_OUT ) {
2018-09-26 12:39:04 +00:00
return ;
}
if ( nextStatus = = Account . State . ONLINE ) {
this . attempt = 0 ;
}
account . setStatus ( nextStatus ) ;
} else {
return ;
}
}
if ( statusListener ! = null ) {
statusListener . onStatusChanged ( account ) ;
}
}
2020-12-01 19:31:30 +00:00
public Jid getJidForCommand ( final String node ) {
synchronized ( this . commands ) {
return this . commands . get ( node ) ;
}
}
2018-09-26 12:39:04 +00:00
public void prepareNewConnection ( ) {
this . lastConnect = SystemClock . elapsedRealtime ( ) ;
this . lastPingSent = SystemClock . elapsedRealtime ( ) ;
this . lastDiscoStarted = Long . MAX_VALUE ;
this . mWaitingForSmCatchup . set ( false ) ;
this . changeStatus ( Account . State . CONNECTING ) ;
}
public boolean isWaitingForSmCatchup ( ) {
return mWaitingForSmCatchup . get ( ) ;
}
public void incrementSmCatchupMessageCounter ( ) {
this . mSmCatchupMessageCounter . incrementAndGet ( ) ;
}
protected void connect ( ) {
if ( mXmppConnectionService . areMessagesInitialized ( ) ) {
mXmppConnectionService . resetSendingToWaiting ( account ) ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) . toString ( ) + " : connecting " ) ;
features . encryptionEnabled = false ;
2022-09-25 12:13:04 +00:00
this . inSmacksSession = false ;
this . quickStartInProgress = false ;
this . isBound = false ;
2018-09-26 12:39:04 +00:00
this . attempt + + ;
2022-09-25 12:13:04 +00:00
this . verifiedHostname = null ; // will be set if user entered hostname is being used or hostname was verified
2022-09-03 18:17:29 +00:00
// with dnssec
2018-09-26 12:39:04 +00:00
try {
Socket localSocket ;
shouldAuthenticate = ! account . isOptionSet ( Account . OPTION_REGISTER ) ;
this . changeStatus ( Account . State . CONNECTING ) ;
final boolean useTor = mXmppConnectionService . useTorToConnect ( ) | | account . isOnion ( ) ;
final boolean extended = mXmppConnectionService . showExtendedConnectionOptions ( ) ;
if ( useTor ) {
String destination ;
2019-01-25 07:48:46 +00:00
if ( account . getHostname ( ) . isEmpty ( ) | | account . isOnion ( ) ) {
2018-09-26 12:39:04 +00:00
destination = account . getServer ( ) ;
} else {
destination = account . getHostname ( ) ;
this . verifiedHostname = destination ;
}
2019-09-05 10:08:58 +00:00
final int port = account . getPort ( ) ;
final boolean directTls = Resolver . useDirectTls ( port ) ;
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : connect to "
+ destination
+ " via Tor. directTls= "
+ directTls ) ;
2019-09-05 10:08:58 +00:00
localSocket = SocksSocketFactory . createSocketOverTor ( destination , port ) ;
if ( directTls ) {
localSocket = upgradeSocketToTls ( localSocket ) ;
features . encryptionEnabled = true ;
}
2018-09-26 12:39:04 +00:00
try {
startXmpp ( localSocket ) ;
2022-09-25 12:13:04 +00:00
} catch ( final InterruptedException e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : thread was interrupted before beginning stream " ) ;
2018-09-26 12:39:04 +00:00
return ;
2022-09-25 12:13:04 +00:00
} catch ( final Exception e ) {
throw new IOException ( " Could not start stream " , e ) ;
2018-09-26 12:39:04 +00:00
}
} else {
2020-05-17 08:24:46 +00:00
final String domain = account . getServer ( ) ;
2018-10-01 15:07:37 +00:00
final List < Resolver . Result > results ;
final boolean hardcoded = extended & & ! account . getHostname ( ) . isEmpty ( ) ;
if ( hardcoded ) {
results = Resolver . fromHardCoded ( account . getHostname ( ) , account . getPort ( ) ) ;
} else {
results = Resolver . resolve ( domain ) ;
}
2018-09-26 12:39:04 +00:00
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : Thread was interrupted " ) ;
return ;
}
if ( results . size ( ) = = 0 ) {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : Resolver results were empty " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
2018-10-01 15:07:37 +00:00
final Resolver . Result storedBackupResult ;
if ( hardcoded ) {
storedBackupResult = null ;
} else {
2022-09-03 10:16:06 +00:00
storedBackupResult =
mXmppConnectionService . databaseBackend . findResolverResult ( domain ) ;
2018-10-01 15:07:37 +00:00
if ( storedBackupResult ! = null & & ! results . contains ( storedBackupResult ) ) {
results . add ( storedBackupResult ) ;
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : loaded backup resolver result from db: "
+ storedBackupResult ) ;
2018-10-01 15:07:37 +00:00
}
2018-09-26 12:39:04 +00:00
}
2023-10-13 06:29:23 +00:00
final Resolver . Result seeOtherHost = this . seeOtherHostResolverResult ;
if ( seeOtherHost ! = null ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : injected see-other-host on position 0 " ) ;
results . add ( 0 , seeOtherHost ) ;
}
for ( final Iterator < Resolver . Result > iterator = results . iterator ( ) ;
2022-09-03 10:16:06 +00:00
iterator . hasNext ( ) ; ) {
2018-09-26 12:39:04 +00:00
final Resolver . Result result = iterator . next ( ) ;
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : Thread was interrupted " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
try {
// if tls is true, encryption is implied and must not be started
features . encryptionEnabled = result . isDirectTls ( ) ;
2022-09-03 10:16:06 +00:00
verifiedHostname =
result . isAuthenticated ( ) ? result . getHostname ( ) . toString ( ) : null ;
2018-09-26 12:39:04 +00:00
final InetSocketAddress addr ;
if ( result . getIp ( ) ! = null ) {
addr = new InetSocketAddress ( result . getIp ( ) , result . getPort ( ) ) ;
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( )
+ " : using values from resolver "
+ ( result . getHostname ( ) = = null
? " "
: result . getHostname ( ) . toString ( ) + " / " )
+ result . getIp ( ) . getHostAddress ( )
+ " : "
+ result . getPort ( )
+ " tls: "
+ features . encryptionEnabled ) ;
2018-09-26 12:39:04 +00:00
} else {
2022-09-03 10:16:06 +00:00
addr =
new InetSocketAddress (
IDN . toASCII ( result . getHostname ( ) . toString ( ) ) ,
result . getPort ( ) ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( )
+ " : using values from resolver "
+ result . getHostname ( ) . toString ( )
+ " : "
+ result . getPort ( )
+ " tls: "
+ features . encryptionEnabled ) ;
2018-09-26 12:39:04 +00:00
}
2019-09-05 10:08:58 +00:00
localSocket = new Socket ( ) ;
localSocket . connect ( addr , Config . SOCKET_TIMEOUT * 1000 ) ;
2018-09-26 12:39:04 +00:00
2019-09-05 10:08:58 +00:00
if ( features . encryptionEnabled ) {
localSocket = upgradeSocketToTls ( localSocket ) ;
2018-09-26 12:39:04 +00:00
}
2019-09-05 10:08:58 +00:00
2018-10-04 09:20:02 +00:00
localSocket . setSoTimeout ( Config . SOCKET_TIMEOUT * 1000 ) ;
2018-09-26 12:39:04 +00:00
if ( startXmpp ( localSocket ) ) {
2022-09-03 10:16:06 +00:00
localSocket . setSoTimeout (
0 ) ; // reset to 0; once the connection is established we don’ t
2022-09-03 18:17:29 +00:00
// want this
2018-10-16 10:23:27 +00:00
if ( ! hardcoded & & ! result . equals ( storedBackupResult ) ) {
2022-09-03 10:16:06 +00:00
mXmppConnectionService . databaseBackend . saveResolverResult (
domain , result ) ;
2018-09-26 12:39:04 +00:00
}
2023-10-13 06:29:23 +00:00
this . currentResolverResult = result ;
this . seeOtherHostResolverResult = null ;
2018-09-26 12:39:04 +00:00
break ; // successfully connected to server that speaks xmpp
} else {
2019-01-19 10:47:21 +00:00
FileBackend . close ( localSocket ) ;
throw new StateChangingException ( Account . State . STREAM_OPENING_ERROR ) ;
2018-09-26 12:39:04 +00:00
}
} catch ( final StateChangingException e ) {
2019-01-19 10:47:21 +00:00
if ( ! iterator . hasNext ( ) ) {
throw e ;
}
2018-09-26 12:39:04 +00:00
} catch ( InterruptedException e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : thread was interrupted before beginning stream " ) ;
2018-09-26 12:39:04 +00:00
return ;
} catch ( final Throwable e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( )
+ " : "
+ e . getMessage ( )
+ " ( "
+ e . getClass ( ) . getName ( )
+ " ) " ) ;
2018-09-26 12:39:04 +00:00
if ( ! iterator . hasNext ( ) ) {
throw new UnknownHostException ( ) ;
}
}
}
}
processStream ( ) ;
} catch ( final SecurityException e ) {
this . changeStatus ( Account . State . MISSING_INTERNET_PERMISSION ) ;
} catch ( final StateChangingException e ) {
this . changeStatus ( e . state ) ;
2022-09-03 10:16:06 +00:00
} catch ( final UnknownHostException
| ConnectException
| SocksSocketFactory . HostNotFoundException e ) {
2019-09-05 10:08:58 +00:00
this . changeStatus ( Account . State . SERVER_NOT_FOUND ) ;
2018-09-26 12:39:04 +00:00
} catch ( final SocksSocketFactory . SocksProxyNotFoundException e ) {
this . changeStatus ( Account . State . TOR_NOT_AVAILABLE ) ;
2020-05-22 16:23:53 +00:00
} catch ( final IOException | XmlPullParserException e ) {
2018-09-26 12:39:04 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) . toString ( ) + " : " + e . getMessage ( ) ) ;
this . changeStatus ( Account . State . OFFLINE ) ;
this . attempt = Math . max ( 0 , this . attempt - 1 ) ;
} finally {
if ( ! Thread . currentThread ( ) . isInterrupted ( ) ) {
forceCloseSocket ( ) ;
} else {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : not force closing socket because thread was interrupted " ) ;
2018-09-26 12:39:04 +00:00
}
}
}
/ * *
* Starts xmpp protocol , call after connecting to socket
*
* @return true if server returns with valid xmpp , false otherwise
* /
2022-09-25 12:13:04 +00:00
private boolean startXmpp ( final Socket socket ) throws Exception {
2018-09-26 12:39:04 +00:00
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
throw new InterruptedException ( ) ;
}
this . socket = socket ;
tagReader = new XmlReader ( ) ;
if ( tagWriter ! = null ) {
tagWriter . forceClose ( ) ;
}
tagWriter = new TagWriter ( ) ;
tagWriter . setOutputStream ( socket . getOutputStream ( ) ) ;
tagReader . setInputStream ( socket . getInputStream ( ) ) ;
tagWriter . beginDocument ( ) ;
2022-09-25 12:13:04 +00:00
final boolean quickStart ;
if ( socket instanceof SSLSocket ) {
2022-10-14 18:00:36 +00:00
final SSLSocket sslSocket = ( SSLSocket ) socket ;
SSLSockets . log ( account , sslSocket ) ;
quickStart = establishStream ( SSLSockets . version ( sslSocket ) ) ;
2022-09-25 12:13:04 +00:00
} else {
2022-10-14 18:00:36 +00:00
quickStart = establishStream ( SSLSockets . Version . NONE ) ;
2022-09-25 12:13:04 +00:00
}
2018-09-26 12:39:04 +00:00
final Tag tag = tagReader . readTag ( ) ;
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
throw new InterruptedException ( ) ;
}
2022-09-25 12:13:04 +00:00
final boolean success = tag ! = null & & tag . isStart ( " stream " , Namespace . STREAMS ) ;
if ( success & & quickStart ) {
this . quickStartInProgress = true ;
2018-09-26 12:39:04 +00:00
}
2022-09-25 12:13:04 +00:00
return success ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
private SSLSocketFactory getSSLSocketFactory ( )
throws NoSuchAlgorithmException , KeyManagementException {
2022-10-14 18:00:36 +00:00
final SSLContext sc = SSLSockets . getSSLContext ( ) ;
2022-09-03 10:16:06 +00:00
final MemorizingTrustManager trustManager =
this . mXmppConnectionService . getMemorizingTrustManager ( ) ;
2020-05-13 07:38:30 +00:00
final KeyManager [ ] keyManager ;
if ( account . getPrivateKeyAlias ( ) ! = null ) {
2022-09-03 10:16:06 +00:00
keyManager = new KeyManager [ ] { new MyKeyManager ( ) } ;
2018-09-26 12:39:04 +00:00
} else {
keyManager = null ;
}
2020-05-17 08:24:46 +00:00
final String domain = account . getServer ( ) ;
2022-09-03 10:16:06 +00:00
sc . init (
keyManager ,
new X509TrustManager [ ] {
mInteractive
? trustManager . getInteractive ( domain )
: trustManager . getNonInteractive ( domain )
} ,
2022-09-06 07:25:09 +00:00
SECURE_RANDOM ) ;
2021-04-30 07:53:19 +00:00
return sc . getSocketFactory ( ) ;
2018-09-26 12:39:04 +00:00
}
@Override
public void run ( ) {
synchronized ( this ) {
this . mThread = Thread . currentThread ( ) ;
if ( this . mThread . isInterrupted ( ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : aborting connect because thread was interrupted " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
forceCloseSocket ( ) ;
}
connect ( ) ;
}
private void processStream ( ) throws XmlPullParserException , IOException {
final CountDownLatch streamCountDownLatch = new CountDownLatch ( 1 ) ;
this . mStreamCountDownLatch = streamCountDownLatch ;
Tag nextTag = tagReader . readTag ( ) ;
while ( nextTag ! = null & & ! nextTag . isEnd ( " stream " ) ) {
if ( nextTag . isStart ( " error " ) ) {
processStreamError ( nextTag ) ;
2022-10-14 11:13:21 +00:00
} else if ( nextTag . isStart ( " features " , Namespace . STREAMS ) ) {
2018-09-26 12:39:04 +00:00
processStreamFeatures ( nextTag ) ;
2022-08-29 16:53:34 +00:00
} else if ( nextTag . isStart ( " proceed " , Namespace . TLS ) ) {
2018-09-27 07:59:05 +00:00
switchOverToTls ( ) ;
2018-09-26 12:39:04 +00:00
} else if ( nextTag . isStart ( " success " ) ) {
2022-08-29 15:09:52 +00:00
final Element success = tagReader . readElement ( nextTag ) ;
2022-08-30 07:31:06 +00:00
if ( processSuccess ( success ) ) {
2022-08-29 15:09:52 +00:00
break ;
2018-09-26 12:39:04 +00:00
}
2022-08-30 07:31:06 +00:00
2022-08-29 16:53:34 +00:00
} else if ( nextTag . isStart ( " failure " , Namespace . TLS ) ) {
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2018-09-26 12:39:04 +00:00
} else if ( nextTag . isStart ( " failure " ) ) {
final Element failure = tagReader . readElement ( nextTag ) ;
2022-09-07 08:31:11 +00:00
processFailure ( failure ) ;
2022-08-29 16:53:34 +00:00
} else if ( nextTag . isStart ( " continue " , Namespace . SASL_2 ) ) {
2022-09-07 08:31:11 +00:00
// two step sasl2 - we don’ t support this yet
2022-08-29 16:53:34 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_CLIENT ) ;
2018-09-26 12:39:04 +00:00
} else if ( nextTag . isStart ( " challenge " ) ) {
2022-11-17 06:48:09 +00:00
if ( isSecure ( ) & & this . saslMechanism ! = null ) {
final Element challenge = tagReader . readElement ( nextTag ) ;
processChallenge ( challenge ) ;
} else {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : received 'challenge on an unsecure connection " ) ;
throw new StateChangingException ( Account . State . INCOMPATIBLE_CLIENT ) ;
}
2022-09-03 18:17:29 +00:00
} else if ( nextTag . isStart ( " enabled " , Namespace . STREAM_MANAGEMENT ) ) {
2018-09-26 12:39:04 +00:00
final Element enabled = tagReader . readElement ( nextTag ) ;
2022-09-03 18:17:29 +00:00
processEnabled ( enabled ) ;
2018-09-26 12:39:04 +00:00
} else if ( nextTag . isStart ( " resumed " ) ) {
final Element resumed = tagReader . readElement ( nextTag ) ;
2022-08-29 17:22:25 +00:00
processResumed ( resumed ) ;
2018-09-26 12:39:04 +00:00
} else if ( nextTag . isStart ( " r " ) ) {
tagReader . readElement ( nextTag ) ;
if ( Config . EXTENDED_SM_LOGGING ) {
2022-08-29 16:53:34 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : acknowledging stanza # "
+ this . stanzasReceived ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
final AckPacket ack = new AckPacket ( this . stanzasReceived ) ;
2018-09-26 12:39:04 +00:00
tagWriter . writeStanzaAsync ( ack ) ;
} else if ( nextTag . isStart ( " a " ) ) {
boolean accountUiNeedsRefresh = false ;
synchronized ( NotificationService . CATCHUP_LOCK ) {
if ( mWaitingForSmCatchup . compareAndSet ( true , false ) ) {
2018-12-22 12:00:40 +00:00
final int messageCount = mSmCatchupMessageCounter . get ( ) ;
final int pendingIQs = packetCallbacks . size ( ) ;
2022-08-29 16:53:34 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : SM catchup complete (messages= "
+ messageCount
+ " , pending IQs= "
+ pendingIQs
+ " ) " ) ;
2018-09-26 12:39:04 +00:00
accountUiNeedsRefresh = true ;
2018-12-22 12:00:40 +00:00
if ( messageCount > 0 ) {
2022-08-29 16:53:34 +00:00
mXmppConnectionService
. getNotificationService ( )
. finishBacklog ( true , account ) ;
2018-09-26 12:39:04 +00:00
}
}
}
if ( accountUiNeedsRefresh ) {
mXmppConnectionService . updateAccountUi ( ) ;
}
final Element ack = tagReader . readElement ( nextTag ) ;
lastPacketReceived = SystemClock . elapsedRealtime ( ) ;
2022-12-30 09:53:49 +00:00
final boolean acknowledgedMessages ;
synchronized ( this . mStanzaQueue ) {
final Optional < Integer > serverSequence = ack . getOptionalIntAttribute ( " h " ) ;
if ( serverSequence . isPresent ( ) ) {
acknowledgedMessages = acknowledgeStanzaUpTo ( serverSequence . get ( ) ) ;
} else {
acknowledgedMessages = false ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server send ack without sequence number " ) ;
2018-09-26 12:39:04 +00:00
}
2022-12-30 09:53:49 +00:00
}
if ( acknowledgedMessages ) {
mXmppConnectionService . updateConversationUi ( ) ;
2018-09-26 12:39:04 +00:00
}
} else if ( nextTag . isStart ( " failed " ) ) {
2022-08-29 17:44:39 +00:00
final Element failed = tagReader . readElement ( nextTag ) ;
processFailed ( failed , true ) ;
2018-09-26 12:39:04 +00:00
} else if ( nextTag . isStart ( " iq " ) ) {
processIq ( nextTag ) ;
} else if ( nextTag . isStart ( " message " ) ) {
processMessage ( nextTag ) ;
} else if ( nextTag . isStart ( " presence " ) ) {
processPresence ( nextTag ) ;
}
nextTag = tagReader . readTag ( ) ;
}
if ( nextTag ! = null & & nextTag . isEnd ( " stream " ) ) {
streamCountDownLatch . countDown ( ) ;
}
}
2022-11-17 06:48:09 +00:00
private void processChallenge ( final Element challenge ) throws IOException {
2022-09-07 08:31:11 +00:00
final SaslMechanism . Version version ;
try {
version = SaslMechanism . Version . of ( challenge ) ;
} catch ( final IllegalArgumentException e ) {
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
final Element response ;
if ( version = = SaslMechanism . Version . SASL ) {
response = new Element ( " response " , Namespace . SASL ) ;
} else if ( version = = SaslMechanism . Version . SASL_2 ) {
response = new Element ( " response " , Namespace . SASL_2 ) ;
} else {
throw new AssertionError ( " Missing implementation for " + version ) ;
}
try {
response . setContent ( saslMechanism . getResponse ( challenge . getContent ( ) , sslSocketOrNull ( socket ) ) ) ;
} catch ( final SaslMechanism . AuthenticationException e ) {
// TODO: Send auth abort tag.
Log . e ( Config . LOGTAG , e . toString ( ) ) ;
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
}
tagWriter . writeElement ( response ) ;
}
2022-09-03 10:16:06 +00:00
private boolean processSuccess ( final Element success )
throws IOException , XmlPullParserException {
2022-08-30 07:31:06 +00:00
final SaslMechanism . Version version ;
try {
version = SaslMechanism . Version . of ( success ) ;
} catch ( final IllegalArgumentException e ) {
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
2022-11-17 06:48:09 +00:00
final SaslMechanism currentSaslMechanism = this . saslMechanism ;
if ( currentSaslMechanism = = null ) {
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
2022-08-30 07:31:06 +00:00
final String challenge ;
if ( version = = SaslMechanism . Version . SASL ) {
challenge = success . getContent ( ) ;
} else if ( version = = SaslMechanism . Version . SASL_2 ) {
challenge = success . findChildContent ( " additional-data " ) ;
} else {
throw new AssertionError ( " Missing implementation for " + version ) ;
}
try {
2022-11-17 06:48:09 +00:00
currentSaslMechanism . getResponse ( challenge , sslSocketOrNull ( socket ) ) ;
2022-08-30 07:31:06 +00:00
} catch ( final SaslMechanism . AuthenticationException e ) {
Log . e ( Config . LOGTAG , String . valueOf ( e ) ) ;
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
}
Log . d (
Config . LOGTAG ,
2022-09-03 10:16:06 +00:00
account . getJid ( ) . asBareJid ( ) . toString ( ) + " : logged in (using " + version + " ) " ) ;
2022-11-17 06:48:09 +00:00
if ( SaslMechanism . pin ( currentSaslMechanism ) ) {
account . setPinnedMechanism ( currentSaslMechanism ) ;
2022-10-15 18:53:59 +00:00
}
2022-08-30 07:31:06 +00:00
if ( version = = SaslMechanism . Version . SASL_2 ) {
final String authorizationIdentifier =
success . findChildContent ( " authorization-identifier " ) ;
final Jid authorizationJid ;
try {
2022-09-03 10:16:06 +00:00
authorizationJid =
Strings . isNullOrEmpty ( authorizationIdentifier )
? null
: Jid . ofEscaped ( authorizationIdentifier ) ;
2022-08-30 07:31:06 +00:00
} catch ( final IllegalArgumentException e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : SASL 2.0 authorization identifier was not a valid jid " ) ;
2022-08-30 07:31:06 +00:00
throw new StateChangingException ( Account . State . BIND_FAILURE ) ;
}
if ( authorizationJid = = null ) {
throw new StateChangingException ( Account . State . BIND_FAILURE ) ;
}
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : SASL 2.0 authorization identifier was "
+ authorizationJid ) ;
if ( ! account . getJid ( ) . getDomain ( ) . equals ( authorizationJid . getDomain ( ) ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server tried to re-assign domain to "
+ authorizationJid . getDomain ( ) ) ;
2022-08-30 07:31:06 +00:00
throw new StateChangingError ( Account . State . BIND_FAILURE ) ;
}
if ( authorizationJid . isFullJid ( ) & & account . setJid ( authorizationJid ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : jid changed during SASL 2.0. updating database " ) ;
2022-08-30 07:31:06 +00:00
}
2022-09-03 18:17:29 +00:00
final Element bound = success . findChild ( " bound " , Namespace . BIND2 ) ;
2022-12-16 07:07:41 +00:00
final Element resumed = success . findChild ( " resumed " , Namespace . STREAM_MANAGEMENT ) ;
final Element failed = success . findChild ( " failed " , Namespace . STREAM_MANAGEMENT ) ;
2022-10-15 10:27:38 +00:00
final Element tokenWrapper = success . findChild ( " token " , Namespace . FAST ) ;
final String token = tokenWrapper = = null ? null : tokenWrapper . getAttribute ( " token " ) ;
2022-10-14 11:13:21 +00:00
if ( bound ! = null & & resumed ! = null ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server sent bound and resumed in SASL2 success " ) ;
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
2022-08-30 07:31:06 +00:00
if ( resumed ! = null & & streamId ! = null ) {
2023-05-02 13:35:27 +00:00
if ( this . boundStreamFeatures ! = null ) {
this . streamFeatures = this . boundStreamFeatures ;
Log . d ( Config . LOGTAG , " putting previous stream features back in place: " + XmlHelper . printElementNames ( this . boundStreamFeatures ) ) ;
}
2022-08-30 07:31:06 +00:00
processResumed ( resumed ) ;
} else if ( failed ! = null ) {
processFailed ( failed , false ) ; // wait for new stream features
}
2022-09-03 18:17:29 +00:00
if ( bound ! = null ) {
2022-09-26 07:47:53 +00:00
clearIqCallbacks ( ) ;
2022-09-03 18:17:29 +00:00
this . isBound = true ;
2023-06-25 17:50:40 +00:00
processNopStreamFeatures ( ) ;
this . boundStreamFeatures = this . streamFeatures ;
2022-09-03 18:17:29 +00:00
final Element streamManagementEnabled =
bound . findChild ( " enabled " , Namespace . STREAM_MANAGEMENT ) ;
final Element carbonsEnabled = bound . findChild ( " enabled " , Namespace . CARBONS ) ;
2022-10-14 11:29:59 +00:00
final boolean waitForDisco ;
2022-09-03 18:17:29 +00:00
if ( streamManagementEnabled ! = null ) {
2022-12-30 11:09:16 +00:00
resetOutboundStanzaQueue ( ) ;
2022-09-03 18:17:29 +00:00
processEnabled ( streamManagementEnabled ) ;
2022-10-14 11:29:59 +00:00
waitForDisco = true ;
} else {
2022-10-14 18:00:36 +00:00
//if we did not enable stream management in bind do it now
2022-10-14 11:29:59 +00:00
waitForDisco = enableStreamManagement ( ) ;
2022-09-03 18:17:29 +00:00
}
if ( carbonsEnabled ! = null ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : successfully enabled carbons " ) ;
features . carbonsEnabled = true ;
}
2022-10-14 11:29:59 +00:00
sendPostBindInitialization ( waitForDisco , carbonsEnabled ! = null ) ;
2022-09-03 18:17:29 +00:00
}
2022-10-15 16:56:31 +00:00
final HashedToken . Mechanism tokenMechanism ;
2022-11-17 06:48:09 +00:00
if ( SaslMechanism . hashedToken ( currentSaslMechanism ) ) {
tokenMechanism = ( ( HashedToken ) currentSaslMechanism ) . getTokenMechanism ( ) ;
2022-10-15 16:56:31 +00:00
} else if ( this . hashTokenRequest ! = null ) {
tokenMechanism = this . hashTokenRequest ;
} else {
tokenMechanism = null ;
}
if ( tokenMechanism ! = null & & ! Strings . isNullOrEmpty ( token ) ) {
2023-10-21 12:22:38 +00:00
if ( ChannelBinding . priority ( tokenMechanism . channelBinding ) > = ChannelBindingMechanism . getPriority ( currentSaslMechanism ) ) {
this . account . setFastToken ( tokenMechanism , token ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : storing hashed token " + tokenMechanism ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : not accepting hashed token " + tokenMechanism . name ( ) + " for log in mechanism " + currentSaslMechanism . getMechanism ( ) ) ;
this . account . resetFastToken ( ) ;
}
2022-12-30 08:00:42 +00:00
} else if ( this . hashTokenRequest ! = null ) {
Log . w (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : no response to our hashed token request "
+ this . hashTokenRequest ) ;
2022-10-15 10:27:38 +00:00
}
2022-08-30 07:31:06 +00:00
}
2022-10-15 16:56:31 +00:00
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
2022-09-25 12:13:04 +00:00
this . quickStartInProgress = false ;
2022-08-30 07:31:06 +00:00
if ( version = = SaslMechanism . Version . SASL ) {
tagReader . reset ( ) ;
2022-10-14 18:00:36 +00:00
sendStartStream ( false , true ) ;
2022-08-30 07:31:06 +00:00
final Tag tag = tagReader . readTag ( ) ;
2022-09-25 12:13:04 +00:00
if ( tag ! = null & & tag . isStart ( " stream " , Namespace . STREAMS ) ) {
2022-08-30 07:31:06 +00:00
processStream ( ) ;
return true ;
} else {
throw new StateChangingException ( Account . State . STREAM_OPENING_ERROR ) ;
}
} else {
return false ;
}
}
2022-12-30 11:09:16 +00:00
private void resetOutboundStanzaQueue ( ) {
synchronized ( this . mStanzaQueue ) {
final List < AbstractAcknowledgeableStanza > intermediateStanzas = new ArrayList < > ( ) ;
if ( Config . EXTENDED_SM_LOGGING ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : stanzas sent before auth: "
+ this . stanzasSentBeforeAuthentication ) ;
}
for ( int i = this . stanzasSentBeforeAuthentication + 1 ; i < = this . stanzasSent ; + + i ) {
final AbstractAcknowledgeableStanza stanza = this . mStanzaQueue . get ( i ) ;
if ( stanza ! = null ) {
intermediateStanzas . add ( stanza ) ;
}
}
this . mStanzaQueue . clear ( ) ;
for ( int i = 0 ; i < intermediateStanzas . size ( ) ; + + i ) {
this . mStanzaQueue . put ( i , intermediateStanzas . get ( i ) ) ;
}
this . stanzasSent = intermediateStanzas . size ( ) ;
if ( Config . EXTENDED_SM_LOGGING ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : resetting outbound stanza queue to "
+ this . stanzasSent ) ;
}
}
}
2022-11-17 06:48:09 +00:00
private void processNopStreamFeatures ( ) throws IOException {
final Tag tag = tagReader . readTag ( ) ;
if ( tag ! = null & & tag . isStart ( " features " , Namespace . STREAMS ) ) {
this . streamFeatures = tagReader . readElement ( tag ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : processed NOP stream features after success: "
+ XmlHelper . printElementNames ( this . streamFeatures ) ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received " + tag ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server did not send stream features after SASL2 success " ) ;
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
}
2022-11-16 10:00:43 +00:00
private void processFailure ( final Element failure ) throws IOException {
2022-09-07 08:31:11 +00:00
final SaslMechanism . Version version ;
try {
version = SaslMechanism . Version . of ( failure ) ;
} catch ( final IllegalArgumentException e ) {
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
2022-11-16 10:00:43 +00:00
Log . d ( Config . LOGTAG , failure . toString ( ) ) ;
2022-09-07 08:31:11 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : login failure " + version ) ;
2022-11-01 17:06:32 +00:00
if ( SaslMechanism . hashedToken ( this . saslMechanism ) ) {
2022-11-16 10:00:43 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : resetting token " ) ;
2022-11-01 15:44:05 +00:00
account . resetFastToken ( ) ;
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
}
2022-09-07 08:31:11 +00:00
if ( failure . hasChild ( " temporary-auth-failure " ) ) {
throw new StateChangingException ( Account . State . TEMPORARY_AUTH_FAILURE ) ;
} else if ( failure . hasChild ( " account-disabled " ) ) {
final String text = failure . findChildContent ( " text " ) ;
if ( Strings . isNullOrEmpty ( text ) ) {
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
}
final Matcher matcher = Patterns . AUTOLINK_WEB_URL . matcher ( text ) ;
if ( matcher . find ( ) ) {
final HttpUrl url ;
try {
url = HttpUrl . get ( text . substring ( matcher . start ( ) , matcher . end ( ) ) ) ;
} catch ( final IllegalArgumentException e ) {
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
}
if ( url . isHttps ( ) ) {
this . redirectionUrl = url ;
throw new StateChangingException ( Account . State . PAYMENT_REQUIRED ) ;
}
}
}
2022-11-16 10:00:43 +00:00
if ( SaslMechanism . hashedToken ( this . saslMechanism ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : fast authentication failed. falling back to regular authentication " ) ;
authenticate ( ) ;
} else {
throw new StateChangingException ( Account . State . UNAUTHORIZED ) ;
}
2022-09-07 08:31:11 +00:00
}
2022-09-06 14:28:28 +00:00
private static SSLSocket sslSocketOrNull ( final Socket socket ) {
if ( socket instanceof SSLSocket ) {
return ( SSLSocket ) socket ;
} else {
return null ;
}
}
2022-09-03 18:17:29 +00:00
private void processEnabled ( final Element enabled ) {
final String streamId ;
if ( enabled . getAttributeAsBoolean ( " resume " ) ) {
streamId = enabled . getAttribute ( " id " ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( )
+ " : stream management enabled (resumable) " ) ;
} else {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( ) + " : stream management enabled " ) ;
streamId = null ;
}
this . streamId = streamId ;
this . stanzasReceived = 0 ;
this . inSmacksSession = true ;
final RequestPacket r = new RequestPacket ( ) ;
2022-09-04 07:28:00 +00:00
tagWriter . writeStanzaAsync ( r ) ;
2022-09-03 18:17:29 +00:00
}
2022-08-29 17:30:03 +00:00
private void processResumed ( final Element resumed ) throws StateChangingException {
2022-08-29 17:22:25 +00:00
this . inSmacksSession = true ;
this . isBound = true ;
2022-09-03 10:16:06 +00:00
this . tagWriter . writeStanzaAsync ( new RequestPacket ( ) ) ;
2022-08-29 17:22:25 +00:00
lastPacketReceived = SystemClock . elapsedRealtime ( ) ;
2022-12-30 09:53:49 +00:00
final Optional < Integer > h = resumed . getOptionalIntAttribute ( " h " ) ;
2022-08-29 17:30:03 +00:00
final int serverCount ;
2022-12-30 09:53:49 +00:00
if ( h . isPresent ( ) ) {
serverCount = h . get ( ) ;
} else {
2022-08-29 17:30:03 +00:00
resetStreamId ( ) ;
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
final ArrayList < AbstractAcknowledgeableStanza > failedStanzas = new ArrayList < > ( ) ;
final boolean acknowledgedMessages ;
synchronized ( this . mStanzaQueue ) {
if ( serverCount < stanzasSent ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : session resumed with lost packages " ) ;
stanzasSent = serverCount ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : session resumed " ) ;
2022-08-29 17:22:25 +00:00
}
2022-08-29 17:30:03 +00:00
acknowledgedMessages = acknowledgeStanzaUpTo ( serverCount ) ;
for ( int i = 0 ; i < this . mStanzaQueue . size ( ) ; + + i ) {
failedStanzas . add ( mStanzaQueue . valueAt ( i ) ) ;
2022-08-29 17:22:25 +00:00
}
2022-08-29 17:30:03 +00:00
mStanzaQueue . clear ( ) ;
}
if ( acknowledgedMessages ) {
mXmppConnectionService . updateConversationUi ( ) ;
}
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : resending " + failedStanzas . size ( ) + " stanzas " ) ;
for ( final AbstractAcknowledgeableStanza packet : failedStanzas ) {
if ( packet instanceof MessagePacket ) {
MessagePacket message = ( MessagePacket ) packet ;
mXmppConnectionService . markMessage (
account ,
message . getTo ( ) . asBareJid ( ) ,
message . getId ( ) ,
Message . STATUS_UNSEND ) ;
2022-08-29 17:22:25 +00:00
}
2022-08-29 17:30:03 +00:00
sendPacket ( packet ) ;
2022-08-29 17:22:25 +00:00
}
2022-09-26 05:53:48 +00:00
changeStatusToOnline ( ) ;
}
private void changeStatusToOnline ( ) {
2022-08-29 17:22:25 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : online with resource " + account . getResource ( ) ) ;
changeStatus ( Account . State . ONLINE ) ;
}
2022-08-29 17:44:39 +00:00
private void processFailed ( final Element failed , final boolean sendBindRequest ) {
2022-12-30 09:53:49 +00:00
final Optional < Integer > serverCount = failed . getOptionalIntAttribute ( " h " ) ;
if ( serverCount . isPresent ( ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : resumption failed but server acknowledged stanza # "
+ serverCount . get ( ) ) ;
final boolean acknowledgedMessages ;
synchronized ( this . mStanzaQueue ) {
acknowledgedMessages = acknowledgeStanzaUpTo ( serverCount . get ( ) ) ;
2022-08-29 17:44:39 +00:00
}
2022-12-30 09:53:49 +00:00
if ( acknowledgedMessages ) {
mXmppConnectionService . updateConversationUi ( ) ;
}
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : resumption failed " ) ;
2022-08-29 17:44:39 +00:00
}
resetStreamId ( ) ;
if ( sendBindRequest ) {
sendBindRequest ( ) ;
}
}
2022-12-30 09:53:49 +00:00
private boolean acknowledgeStanzaUpTo ( final int serverCount ) {
2018-09-26 12:39:04 +00:00
if ( serverCount > stanzasSent ) {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
" server acknowledged more stanzas than we sent. serverCount= "
+ serverCount
+ " , ourCount= "
+ stanzasSent ) ;
2018-09-26 12:39:04 +00:00
}
boolean acknowledgedMessages = false ;
for ( int i = 0 ; i < mStanzaQueue . size ( ) ; + + i ) {
if ( serverCount > = mStanzaQueue . keyAt ( i ) ) {
if ( Config . EXTENDED_SM_LOGGING ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server acknowledged stanza # "
+ mStanzaQueue . keyAt ( i ) ) ;
2018-09-26 12:39:04 +00:00
}
2021-02-12 09:35:13 +00:00
final AbstractAcknowledgeableStanza stanza = mStanzaQueue . valueAt ( i ) ;
2018-09-26 12:39:04 +00:00
if ( stanza instanceof MessagePacket & & acknowledgedListener ! = null ) {
2021-02-12 09:35:13 +00:00
final MessagePacket packet = ( MessagePacket ) stanza ;
final String id = packet . getId ( ) ;
final Jid to = packet . getTo ( ) ;
if ( id ! = null & & to ! = null ) {
2022-09-03 10:16:06 +00:00
acknowledgedMessages | =
acknowledgedListener . onMessageAcknowledged ( account , to , id ) ;
2021-02-12 09:35:13 +00:00
}
2018-09-26 12:39:04 +00:00
}
mStanzaQueue . removeAt ( i ) ;
i - - ;
}
}
return acknowledgedMessages ;
}
2022-09-03 10:16:06 +00:00
private @NonNull Element processPacket ( final Tag currentTag , final int packetType )
throws IOException {
2020-04-01 11:25:52 +00:00
final Element element ;
2018-09-26 12:39:04 +00:00
switch ( packetType ) {
case PACKET_IQ :
element = new IqPacket ( ) ;
break ;
case PACKET_MESSAGE :
element = new MessagePacket ( ) ;
break ;
case PACKET_PRESENCE :
element = new PresencePacket ( ) ;
break ;
default :
throw new AssertionError ( " Should never encounter invalid type " ) ;
}
element . setAttributes ( currentTag . getAttributes ( ) ) ;
Tag nextTag = tagReader . readTag ( ) ;
if ( nextTag = = null ) {
throw new IOException ( " interrupted mid tag " ) ;
}
while ( ! nextTag . isEnd ( element . getName ( ) ) ) {
if ( ! nextTag . isNo ( ) ) {
2020-04-01 11:25:52 +00:00
element . addChild ( tagReader . readElement ( nextTag ) ) ;
2018-09-26 12:39:04 +00:00
}
nextTag = tagReader . readTag ( ) ;
if ( nextTag = = null ) {
throw new IOException ( " interrupted mid tag " ) ;
}
}
if ( stanzasReceived = = Integer . MAX_VALUE ) {
resetStreamId ( ) ;
throw new IOException ( " time to restart the session. cant handle >2 billion pcks " ) ;
}
if ( inSmacksSession ) {
+ + stanzasReceived ;
} else if ( features . sm ( ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : not counting stanza( "
+ element . getClass ( ) . getSimpleName ( )
+ " ). Not in smacks session. " ) ;
2018-09-26 12:39:04 +00:00
}
lastPacketReceived = SystemClock . elapsedRealtime ( ) ;
if ( Config . BACKGROUND_STANZA_LOGGING & & mXmppConnectionService . checkListeners ( ) ) {
Log . d ( Config . LOGTAG , " [background stanza] " + element ) ;
}
2020-07-18 14:14:05 +00:00
if ( element instanceof IqPacket
& & ( ( ( IqPacket ) element ) . getType ( ) = = IqPacket . TYPE . SET )
& & element . hasChild ( " jingle " , Namespace . JINGLE ) ) {
2020-04-01 11:25:52 +00:00
return JinglePacket . upgrade ( ( IqPacket ) element ) ;
} else {
return element ;
}
2018-09-26 12:39:04 +00:00
}
2020-04-02 09:50:09 +00:00
private void processIq ( final Tag currentTag ) throws IOException {
2018-09-26 12:39:04 +00:00
final IqPacket packet = ( IqPacket ) processPacket ( currentTag , PACKET_IQ ) ;
if ( ! packet . valid ( ) ) {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
" encountered invalid iq from=' "
+ packet . getFrom ( )
+ " ' to=' "
+ packet . getTo ( )
+ " ' " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
if ( packet instanceof JinglePacket ) {
if ( this . jingleListener ! = null ) {
this . jingleListener . onJinglePacketReceived ( account , ( JinglePacket ) packet ) ;
}
} else {
OnIqPacketReceived callback = null ;
synchronized ( this . packetCallbacks ) {
2022-09-03 10:16:06 +00:00
final Pair < IqPacket , OnIqPacketReceived > packetCallbackDuple =
packetCallbacks . get ( packet . getId ( ) ) ;
2020-04-02 09:50:09 +00:00
if ( packetCallbackDuple ! = null ) {
2018-09-26 12:39:04 +00:00
// Packets to the server should have responses from the server
if ( packetCallbackDuple . first . toServer ( account ) ) {
if ( packet . fromServer ( account ) ) {
callback = packetCallbackDuple . second ;
packetCallbacks . remove ( packet . getId ( ) ) ;
} else {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( )
+ " : ignoring spoofed iq packet " ) ;
2018-09-26 12:39:04 +00:00
}
} else {
2022-09-03 10:16:06 +00:00
if ( packet . getFrom ( ) ! = null
& & packet . getFrom ( ) . equals ( packetCallbackDuple . first . getTo ( ) ) ) {
2018-09-26 12:39:04 +00:00
callback = packetCallbackDuple . second ;
packetCallbacks . remove ( packet . getId ( ) ) ;
} else {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) . toString ( )
+ " : ignoring spoofed iq packet " ) ;
2018-09-26 12:39:04 +00:00
}
}
2022-09-03 10:16:06 +00:00
} else if ( packet . getType ( ) = = IqPacket . TYPE . GET
| | packet . getType ( ) = = IqPacket . TYPE . SET ) {
2018-09-26 12:39:04 +00:00
callback = this . unregisteredIqListener ;
}
}
if ( callback ! = null ) {
try {
callback . onIqPacketReceived ( account , packet ) ;
} catch ( StateChangingError error ) {
throw new StateChangingException ( error . state ) ;
}
}
}
}
2020-12-31 09:27:06 +00:00
private void processMessage ( final Tag currentTag ) throws IOException {
2018-09-26 12:39:04 +00:00
final MessagePacket packet = ( MessagePacket ) processPacket ( currentTag , PACKET_MESSAGE ) ;
if ( ! packet . valid ( ) ) {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
" encountered invalid message from=' "
+ packet . getFrom ( )
+ " ' to=' "
+ packet . getTo ( )
+ " ' " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
this . messageListener . onMessagePacketReceived ( account , packet ) ;
}
2020-12-31 09:27:06 +00:00
private void processPresence ( final Tag currentTag ) throws IOException {
2018-09-26 12:39:04 +00:00
PresencePacket packet = ( PresencePacket ) processPacket ( currentTag , PACKET_PRESENCE ) ;
if ( ! packet . valid ( ) ) {
2022-09-03 10:16:06 +00:00
Log . e (
Config . LOGTAG ,
" encountered invalid presence from=' "
+ packet . getFrom ( )
+ " ' to=' "
+ packet . getTo ( )
+ " ' " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
this . presenceListener . onPresencePacketReceived ( account , packet ) ;
}
private void sendStartTLS ( ) throws IOException {
final Tag startTLS = Tag . empty ( " starttls " ) ;
startTLS . setAttribute ( " xmlns " , Namespace . TLS ) ;
tagWriter . writeTag ( startTLS ) ;
}
2018-09-27 07:59:05 +00:00
private void switchOverToTls ( ) throws XmlPullParserException , IOException {
2018-09-26 12:39:04 +00:00
tagReader . readTag ( ) ;
2019-09-05 10:08:58 +00:00
final Socket socket = this . socket ;
final SSLSocket sslSocket = upgradeSocketToTls ( socket ) ;
2023-10-23 12:40:40 +00:00
this . socket = sslSocket ;
this . tagReader . setInputStream ( sslSocket . getInputStream ( ) ) ;
this . tagWriter . setOutputStream ( sslSocket . getOutputStream ( ) ) ;
2019-09-05 10:08:58 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : TLS connection established " ) ;
2022-09-26 07:47:53 +00:00
final boolean quickStart ;
try {
2022-10-14 18:00:36 +00:00
quickStart = establishStream ( SSLSockets . version ( sslSocket ) ) ;
2022-09-26 07:47:53 +00:00
} catch ( final InterruptedException e ) {
return ;
}
2022-09-25 12:13:04 +00:00
if ( quickStart ) {
this . quickStartInProgress = true ;
}
2019-09-05 10:08:58 +00:00
features . encryptionEnabled = true ;
final Tag tag = tagReader . readTag ( ) ;
2022-09-25 12:13:04 +00:00
if ( tag ! = null & & tag . isStart ( " stream " , Namespace . STREAMS ) ) {
2022-10-14 18:00:36 +00:00
SSLSockets . log ( account , sslSocket ) ;
2019-09-05 10:08:58 +00:00
processStream ( ) ;
} else {
throw new StateChangingException ( Account . State . STREAM_OPENING_ERROR ) ;
}
sslSocket . close ( ) ;
}
2018-09-26 12:39:04 +00:00
2019-09-05 10:08:58 +00:00
private SSLSocket upgradeSocketToTls ( final Socket socket ) throws IOException {
2021-04-30 07:53:19 +00:00
final SSLSocketFactory sslSocketFactory ;
2019-09-05 10:08:58 +00:00
try {
2021-04-30 07:53:19 +00:00
sslSocketFactory = getSSLSocketFactory ( ) ;
2019-09-05 10:08:58 +00:00
} catch ( final NoSuchAlgorithmException | KeyManagementException e ) {
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
}
final InetAddress address = socket . getInetAddress ( ) ;
2022-09-03 10:16:06 +00:00
final SSLSocket sslSocket =
( SSLSocket )
sslSocketFactory . createSocket (
socket , address . getHostAddress ( ) , socket . getPort ( ) , true ) ;
2022-10-14 18:00:36 +00:00
SSLSockets . setSecurity ( sslSocket ) ;
SSLSockets . setHostname ( sslSocket , IDN . toASCII ( account . getServer ( ) ) ) ;
SSLSockets . setApplicationProtocol ( sslSocket , " xmpp-client " ) ;
2021-04-30 07:53:19 +00:00
final XmppDomainVerifier xmppDomainVerifier = new XmppDomainVerifier ( ) ;
2021-05-03 06:28:03 +00:00
try {
2022-09-03 10:16:06 +00:00
if ( ! xmppDomainVerifier . verify (
account . getServer ( ) , this . verifiedHostname , sslSocket . getSession ( ) ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : TLS certificate domain verification failed " ) ;
2021-05-03 06:28:03 +00:00
FileBackend . close ( sslSocket ) ;
throw new StateChangingException ( Account . State . TLS_ERROR_DOMAIN ) ;
}
} catch ( final SSLPeerUnverifiedException e ) {
2019-09-05 10:08:58 +00:00
FileBackend . close ( sslSocket ) ;
2021-05-03 06:28:03 +00:00
throw new StateChangingException ( Account . State . TLS_ERROR ) ;
2018-09-26 12:39:04 +00:00
}
2019-09-05 10:08:58 +00:00
return sslSocket ;
2018-09-26 12:39:04 +00:00
}
2020-12-31 09:27:06 +00:00
private void processStreamFeatures ( final Tag currentTag ) throws IOException {
2018-09-26 12:39:04 +00:00
this . streamFeatures = tagReader . readElement ( currentTag ) ;
2022-11-17 06:48:09 +00:00
final boolean isSecure = isSecure ( ) ;
2018-09-26 12:39:04 +00:00
final boolean needsBinding = ! isBound & & ! account . isOptionSet ( Account . OPTION_REGISTER ) ;
2022-09-25 12:13:04 +00:00
if ( this . quickStartInProgress ) {
2022-09-26 05:53:48 +00:00
if ( this . streamFeatures . hasChild ( " authentication " , Namespace . SASL_2 ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : quick start in progress. ignoring features: "
+ XmlHelper . printElementNames ( this . streamFeatures ) ) ;
2022-11-01 17:06:32 +00:00
if ( SaslMechanism . hashedToken ( this . saslMechanism ) ) {
return ;
}
if ( isFastTokenAvailable (
this . streamFeatures . findChild ( " authentication " , Namespace . SASL_2 ) ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : fast token available; resetting quick start " ) ;
account . setOption ( Account . OPTION_QUICKSTART_AVAILABLE , false ) ;
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
}
2022-09-26 05:53:48 +00:00
return ;
}
2022-11-01 17:06:32 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server lost support for SASL 2. quick start not possible " ) ;
2022-09-26 05:53:48 +00:00
this . account . setOption ( Account . OPTION_QUICKSTART_AVAILABLE , false ) ;
2022-11-01 17:06:32 +00:00
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
2022-09-26 05:53:48 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
if ( this . streamFeatures . hasChild ( " starttls " , Namespace . TLS )
2022-08-29 13:09:53 +00:00
& & ! features . encryptionEnabled ) {
2018-09-26 12:39:04 +00:00
sendStartTLS ( ) ;
2022-08-29 13:09:53 +00:00
} else if ( this . streamFeatures . hasChild ( " register " , Namespace . REGISTER_STREAM_FEATURE )
& & account . isOptionSet ( Account . OPTION_REGISTER ) ) {
2018-09-26 12:39:04 +00:00
if ( isSecure ) {
2020-01-09 13:13:05 +00:00
register ( ) ;
2018-09-26 12:39:04 +00:00
} else {
2022-08-29 13:09:53 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : unable to find STARTTLS for registration process "
+ XmlHelper . printElementNames ( this . streamFeatures ) ) ;
2018-09-26 12:39:04 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
2022-08-29 13:09:53 +00:00
} else if ( ! this . streamFeatures . hasChild ( " register " , Namespace . REGISTER_STREAM_FEATURE )
& & account . isOptionSet ( Account . OPTION_REGISTER ) ) {
2018-09-26 12:39:04 +00:00
throw new StateChangingException ( Account . State . REGISTRATION_NOT_SUPPORTED ) ;
2022-10-14 18:00:36 +00:00
} else if ( this . streamFeatures . hasChild ( " authentication " , Namespace . SASL_2 )
2022-08-29 13:09:53 +00:00
& & shouldAuthenticate
& & isSecure ) {
authenticate ( SaslMechanism . Version . SASL_2 ) ;
} else if ( this . streamFeatures . hasChild ( " mechanisms " , Namespace . SASL )
& & shouldAuthenticate
& & isSecure ) {
authenticate ( SaslMechanism . Version . SASL ) ;
2022-09-03 10:16:06 +00:00
} else if ( this . streamFeatures . hasChild ( " sm " , Namespace . STREAM_MANAGEMENT )
2022-09-04 07:28:00 +00:00
& & streamId ! = null
& & ! inSmacksSession ) {
2018-09-26 12:39:04 +00:00
if ( Config . EXTENDED_SM_LOGGING ) {
2022-08-29 13:09:53 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : resuming after stanza # "
+ stanzasReceived ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
final ResumePacket resume = new ResumePacket ( this . streamId , stanzasReceived ) ;
2018-09-26 12:39:04 +00:00
this . mSmCatchupMessageCounter . set ( 0 ) ;
this . mWaitingForSmCatchup . set ( true ) ;
this . tagWriter . writeStanzaAsync ( resume ) ;
} else if ( needsBinding ) {
2022-08-29 13:09:53 +00:00
if ( this . streamFeatures . hasChild ( " bind " , Namespace . BIND ) & & isSecure ) {
2018-09-26 12:39:04 +00:00
sendBindRequest ( ) ;
} else {
2022-08-29 13:09:53 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : unable to find bind feature "
+ XmlHelper . printElementNames ( this . streamFeatures ) ) ;
2018-09-26 12:39:04 +00:00
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
2022-08-29 17:22:25 +00:00
} else {
2023-05-02 13:35:27 +00:00
2022-08-29 17:22:25 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
2022-11-17 09:52:30 +00:00
+ " : received NOP stream features: "
2022-09-04 07:28:00 +00:00
+ XmlHelper . printElementNames ( this . streamFeatures ) ) ;
2018-09-26 12:39:04 +00:00
}
}
2022-11-16 10:00:43 +00:00
private void authenticate ( ) throws IOException {
2022-11-17 06:48:09 +00:00
final boolean isSecure = isSecure ( ) ;
2023-11-20 18:28:58 +00:00
if ( isSecure & & this . streamFeatures . hasChild ( " authentication " , Namespace . SASL_2 ) ) {
authenticate ( SaslMechanism . Version . SASL_2 ) ;
2022-11-16 10:00:43 +00:00
} else if ( isSecure & & this . streamFeatures . hasChild ( " mechanisms " , Namespace . SASL ) ) {
authenticate ( SaslMechanism . Version . SASL ) ;
} else {
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
}
2022-11-17 06:48:09 +00:00
private boolean isSecure ( ) {
return features . encryptionEnabled | | Config . ALLOW_NON_TLS_CONNECTIONS | | account . isOnion ( ) ;
}
2022-08-29 13:09:53 +00:00
private void authenticate ( final SaslMechanism . Version version ) throws IOException {
2022-09-24 09:59:53 +00:00
final Element authElement ;
2022-09-15 11:10:15 +00:00
if ( version = = SaslMechanism . Version . SASL ) {
2022-09-24 09:59:53 +00:00
authElement = this . streamFeatures . findChild ( " mechanisms " , Namespace . SASL ) ;
2022-09-15 11:10:15 +00:00
} else {
2022-09-24 09:59:53 +00:00
authElement = this . streamFeatures . findChild ( " authentication " , Namespace . SASL_2 ) ;
2022-09-15 11:10:15 +00:00
}
2022-10-14 18:00:36 +00:00
final Collection < String > mechanisms = SaslMechanism . mechanisms ( authElement ) ;
2022-09-06 12:53:12 +00:00
final Element cbElement =
this . streamFeatures . findChild ( " sasl-channel-binding " , Namespace . CHANNEL_BINDING ) ;
2022-10-14 18:00:36 +00:00
final Collection < ChannelBinding > channelBindings = ChannelBinding . of ( cbElement ) ;
2022-09-06 07:25:09 +00:00
final SaslMechanism . Factory factory = new SaslMechanism . Factory ( account ) ;
2022-10-15 18:53:59 +00:00
final SaslMechanism saslMechanism = factory . of ( mechanisms , channelBindings , version , SSLSockets . version ( this . socket ) ) ;
this . saslMechanism = validate ( saslMechanism , mechanisms ) ;
2022-09-25 12:13:04 +00:00
final boolean quickStartAvailable ;
2022-10-15 18:53:59 +00:00
final String firstMessage = this . saslMechanism . getClientFirstMessage ( sslSocketOrNull ( this . socket ) ) ;
final boolean usingFast = SaslMechanism . hashedToken ( this . saslMechanism ) ;
2022-08-29 15:09:52 +00:00
final Element authenticate ;
if ( version = = SaslMechanism . Version . SASL ) {
authenticate = new Element ( " auth " , Namespace . SASL ) ;
if ( ! Strings . isNullOrEmpty ( firstMessage ) ) {
authenticate . setContent ( firstMessage ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-25 12:13:04 +00:00
quickStartAvailable = false ;
2022-08-29 15:09:52 +00:00
} else if ( version = = SaslMechanism . Version . SASL_2 ) {
2022-09-24 09:59:53 +00:00
final Element inline = authElement . findChild ( " inline " , Namespace . SASL_2 ) ;
2022-12-16 07:07:41 +00:00
final boolean sm = inline ! = null & & inline . hasChild ( " sm " , Namespace . STREAM_MANAGEMENT ) ;
2022-10-15 16:56:31 +00:00
final HashedToken . Mechanism hashTokenRequest ;
if ( usingFast ) {
hashTokenRequest = null ;
} else {
final Element fast = inline = = null ? null : inline . findChild ( " fast " , Namespace . FAST ) ;
final Collection < String > fastMechanisms = SaslMechanism . mechanisms ( fast ) ;
hashTokenRequest =
HashedToken . Mechanism . best ( fastMechanisms , SSLSockets . version ( this . socket ) ) ;
}
2022-09-25 12:13:04 +00:00
final Collection < String > bindFeatures = Bind2 . features ( inline ) ;
quickStartAvailable =
sm
& & bindFeatures ! = null
& & bindFeatures . containsAll ( Bind2 . QUICKSTART_FEATURES ) ;
2022-09-26 07:47:53 +00:00
if ( bindFeatures ! = null ) {
try {
mXmppConnectionService . restoredFromDatabaseLatch . await ( ) ;
} catch ( final InterruptedException e ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : interrupted while waiting for DB restore during SASL2 bind " ) ;
return ;
}
}
2022-10-15 10:27:38 +00:00
this . hashTokenRequest = hashTokenRequest ;
2022-10-15 16:56:31 +00:00
authenticate = generateAuthenticationRequest ( firstMessage , usingFast , hashTokenRequest , bindFeatures , sm ) ;
2018-09-26 12:39:04 +00:00
} else {
2022-08-29 15:09:52 +00:00
throw new AssertionError ( " Missing implementation for " + version ) ;
2018-09-26 12:39:04 +00:00
}
2022-08-29 15:09:52 +00:00
2022-09-25 12:13:04 +00:00
if ( account . setOption ( Account . OPTION_QUICKSTART_AVAILABLE , quickStartAvailable ) ) {
2022-11-01 17:06:32 +00:00
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
2022-09-25 12:13:04 +00:00
}
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . toString ( )
+ " : Authenticating with "
+ version
+ " / "
2022-10-15 18:53:59 +00:00
+ this . saslMechanism . getMechanism ( ) ) ;
authenticate . setAttribute ( " mechanism " , this . saslMechanism . getMechanism ( ) ) ;
2022-12-30 11:09:16 +00:00
synchronized ( this . mStanzaQueue ) {
this . stanzasSentBeforeAuthentication = this . stanzasSent ;
tagWriter . writeElement ( authenticate ) ;
}
2018-09-26 12:39:04 +00:00
}
2022-11-01 17:06:32 +00:00
private static boolean isFastTokenAvailable ( final Element authentication ) {
final Element inline = authentication = = null ? null : authentication . findChild ( " inline " ) ;
return inline ! = null & & inline . hasChild ( " fast " , Namespace . FAST ) ;
}
2022-10-15 18:53:59 +00:00
@NonNull
private SaslMechanism validate ( final @Nullable SaslMechanism saslMechanism , Collection < String > mechanisms ) throws StateChangingException {
if ( saslMechanism = = null ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : unable to find supported SASL mechanism in "
+ mechanisms ) ;
throw new StateChangingException ( Account . State . INCOMPATIBLE_SERVER ) ;
}
if ( SaslMechanism . hashedToken ( saslMechanism ) ) {
return saslMechanism ;
}
final int pinnedMechanism = account . getPinnedMechanismPriority ( ) ;
if ( pinnedMechanism > saslMechanism . getPriority ( ) ) {
Log . e (
Config . LOGTAG ,
" Auth failed. Authentication mechanism "
+ saslMechanism . getMechanism ( )
+ " has lower priority ( "
+ saslMechanism . getPriority ( )
+ " ) than pinned priority ( "
+ pinnedMechanism
+ " ). Possible downgrade attack? " ) ;
throw new StateChangingException ( Account . State . DOWNGRADE_ATTACK ) ;
}
return saslMechanism ;
}
2022-10-15 16:56:31 +00:00
private Element generateAuthenticationRequest ( final String firstMessage , final boolean usingFast ) {
return generateAuthenticationRequest ( firstMessage , usingFast , null , Bind2 . QUICKSTART_FEATURES , true ) ;
2022-09-24 12:58:49 +00:00
}
private Element generateAuthenticationRequest (
final String firstMessage ,
2022-10-15 16:56:31 +00:00
final boolean usingFast ,
2022-10-15 10:27:38 +00:00
final HashedToken . Mechanism hashedTokenRequest ,
2022-09-24 12:58:49 +00:00
final Collection < String > bind ,
final boolean inlineStreamManagement ) {
final Element authenticate = new Element ( " authenticate " , Namespace . SASL_2 ) ;
if ( ! Strings . isNullOrEmpty ( firstMessage ) ) {
authenticate . addChild ( " initial-response " ) . setContent ( firstMessage ) ;
}
final Element userAgent = authenticate . addChild ( " user-agent " ) ;
2023-01-06 11:41:26 +00:00
userAgent . setAttribute ( " id " , AccountUtils . publicDeviceId ( account ) ) ;
2022-09-24 12:58:49 +00:00
userAgent
. addChild ( " software " )
. setContent ( mXmppConnectionService . getString ( R . string . app_name ) ) ;
if ( ! PhoneHelper . isEmulator ( ) ) {
userAgent
. addChild ( " device " )
. setContent ( String . format ( " %s %s " , Build . MANUFACTURER , Build . MODEL ) ) ;
}
2023-06-25 17:50:40 +00:00
// do not include bind if 'inlinestreamManagment' is missing and we have a streamId
final boolean mayAttemptBind = streamId = = null | | inlineStreamManagement ;
if ( bind ! = null & & mayAttemptBind ) {
2022-09-24 12:58:49 +00:00
authenticate . addChild ( generateBindRequest ( bind ) ) ;
}
if ( inlineStreamManagement & & streamId ! = null ) {
final ResumePacket resume = new ResumePacket ( this . streamId , stanzasReceived ) ;
this . mSmCatchupMessageCounter . set ( 0 ) ;
this . mWaitingForSmCatchup . set ( true ) ;
authenticate . addChild ( resume ) ;
}
2022-10-15 10:27:38 +00:00
if ( hashedTokenRequest ! = null ) {
2022-10-15 16:56:31 +00:00
authenticate
. addChild ( " request-token " , Namespace . FAST )
. setAttribute ( " mechanism " , hashedTokenRequest . name ( ) ) ;
}
if ( usingFast ) {
authenticate . addChild ( " fast " , Namespace . FAST ) ;
2022-10-15 10:27:38 +00:00
}
2022-09-24 12:58:49 +00:00
return authenticate ;
}
2022-09-03 18:17:29 +00:00
private Element generateBindRequest ( final Collection < String > bindFeatures ) {
Log . d ( Config . LOGTAG , " inline bind features: " + bindFeatures ) ;
final Element bind = new Element ( " bind " , Namespace . BIND2 ) ;
2022-09-15 12:28:51 +00:00
bind . addChild ( " tag " ) . setContent ( mXmppConnectionService . getString ( R . string . app_name ) ) ;
2022-09-03 18:17:29 +00:00
if ( bindFeatures . contains ( Namespace . CARBONS ) ) {
2022-10-14 18:00:36 +00:00
bind . addChild ( " enable " , Namespace . CARBONS ) ;
2022-09-03 18:17:29 +00:00
}
if ( bindFeatures . contains ( Namespace . STREAM_MANAGEMENT ) ) {
2022-10-14 18:00:36 +00:00
bind . addChild ( new EnablePacket ( ) ) ;
2022-09-03 18:17:29 +00:00
}
return bind ;
}
2020-01-09 13:13:05 +00:00
private void register ( ) {
2022-09-15 10:22:05 +00:00
final String preAuth = account . getKey ( Account . KEY_PRE_AUTH_REGISTRATION_TOKEN ) ;
2020-01-09 13:13:05 +00:00
if ( preAuth ! = null & & features . invite ( ) ) {
final IqPacket preAuthRequest = new IqPacket ( IqPacket . TYPE . SET ) ;
preAuthRequest . addChild ( " preauth " , Namespace . PARS ) . setAttribute ( " token " , preAuth ) ;
2022-09-03 10:16:06 +00:00
sendUnmodifiedIqPacket (
preAuthRequest ,
( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
sendRegistryRequest ( ) ;
} else {
final String error = response . getErrorCondition ( ) ;
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : failed to pre auth. "
+ error ) ;
throw new StateChangingError ( Account . State . REGISTRATION_INVALID_TOKEN ) ;
}
} ,
true ) ;
2020-01-09 13:13:05 +00:00
} else {
sendRegistryRequest ( ) ;
}
}
2018-09-26 12:39:04 +00:00
private void sendRegistryRequest ( ) {
final IqPacket register = new IqPacket ( IqPacket . TYPE . GET ) ;
2019-04-29 07:51:46 +00:00
register . query ( Namespace . REGISTER ) ;
2020-05-17 08:24:46 +00:00
register . setTo ( account . getDomain ( ) ) ;
2022-09-03 10:16:06 +00:00
sendUnmodifiedIqPacket (
register ,
( account , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
return ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
throw new StateChangingError ( Account . State . REGISTRATION_FAILED ) ;
}
final Element query = packet . query ( Namespace . REGISTER ) ;
if ( query . hasChild ( " username " ) & & ( query . hasChild ( " password " ) ) ) {
final IqPacket register1 = new IqPacket ( IqPacket . TYPE . SET ) ;
final Element username =
new Element ( " username " ) . setContent ( account . getUsername ( ) ) ;
final Element password =
new Element ( " password " ) . setContent ( account . getPassword ( ) ) ;
register1 . query ( Namespace . REGISTER ) . addChild ( username ) ;
register1 . query ( ) . addChild ( password ) ;
register1 . setFrom ( account . getJid ( ) . asBareJid ( ) ) ;
sendUnmodifiedIqPacket ( register1 , registrationResponseListener , true ) ;
} else if ( query . hasChild ( " x " , Namespace . DATA ) ) {
final Data data = Data . parse ( query . findChild ( " x " , Namespace . DATA ) ) ;
final Element blob = query . findChild ( " data " , " urn:xmpp:bob " ) ;
final String id = packet . getId ( ) ;
InputStream is ;
if ( blob ! = null ) {
try {
final String base64Blob = blob . getContent ( ) ;
final byte [ ] strBlob = Base64 . decode ( base64Blob , Base64 . DEFAULT ) ;
is = new ByteArrayInputStream ( strBlob ) ;
} catch ( Exception e ) {
is = null ;
}
2021-03-22 09:39:53 +00:00
} else {
2022-09-03 10:16:06 +00:00
final boolean useTor =
mXmppConnectionService . useTorToConnect ( ) | | account . isOnion ( ) ;
try {
final String url = data . getValue ( " url " ) ;
final String fallbackUrl = data . getValue ( " captcha-fallback-url " ) ;
if ( url ! = null ) {
is = HttpConnectionManager . open ( url , useTor ) ;
} else if ( fallbackUrl ! = null ) {
is = HttpConnectionManager . open ( fallbackUrl , useTor ) ;
} else {
is = null ;
}
} catch ( final IOException e ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : unable to fetch captcha " ,
e ) ;
is = null ;
}
2021-03-22 09:39:53 +00:00
}
2018-09-26 12:39:04 +00:00
2022-09-03 10:16:06 +00:00
if ( is ! = null ) {
Bitmap captcha = BitmapFactory . decodeStream ( is ) ;
try {
if ( mXmppConnectionService . displayCaptchaRequest (
account , id , data , captcha ) ) {
return ;
}
} catch ( Exception e ) {
throw new StateChangingError ( Account . State . REGISTRATION_FAILED ) ;
}
}
throw new StateChangingError ( Account . State . REGISTRATION_FAILED ) ;
} else if ( query . hasChild ( " instructions " )
| | query . hasChild ( " x " , Namespace . OOB ) ) {
final String instructions = query . findChildContent ( " instructions " ) ;
final Element oob = query . findChild ( " x " , Namespace . OOB ) ;
final String url = oob = = null ? null : oob . findChildContent ( " url " ) ;
if ( url ! = null ) {
setAccountCreationFailed ( url ) ;
} else if ( instructions ! = null ) {
final Matcher matcher = Patterns . AUTOLINK_WEB_URL . matcher ( instructions ) ;
if ( matcher . find ( ) ) {
setAccountCreationFailed (
instructions . substring ( matcher . start ( ) , matcher . end ( ) ) ) ;
}
2018-09-26 12:39:04 +00:00
}
throw new StateChangingError ( Account . State . REGISTRATION_FAILED ) ;
}
2022-09-03 10:16:06 +00:00
} ,
true ) ;
2018-09-26 12:39:04 +00:00
}
2021-03-22 09:12:53 +00:00
private void setAccountCreationFailed ( final String url ) {
final HttpUrl httpUrl = url = = null ? null : HttpUrl . parse ( url ) ;
if ( httpUrl ! = null & & httpUrl . isHttps ( ) ) {
this . redirectionUrl = httpUrl ;
throw new StateChangingError ( Account . State . REGISTRATION_WEB ) ;
2018-09-26 12:39:04 +00:00
}
throw new StateChangingError ( Account . State . REGISTRATION_FAILED ) ;
}
2021-03-22 09:12:53 +00:00
public HttpUrl getRedirectionUrl ( ) {
2018-09-26 12:39:04 +00:00
return this . redirectionUrl ;
}
public void resetEverything ( ) {
resetAttemptCount ( true ) ;
resetStreamId ( ) ;
clearIqCallbacks ( ) ;
this . stanzasSent = 0 ;
mStanzaQueue . clear ( ) ;
this . redirectionUrl = null ;
synchronized ( this . disco ) {
disco . clear ( ) ;
}
2020-12-01 19:31:30 +00:00
synchronized ( this . commands ) {
this . commands . clear ( ) ;
}
2022-11-17 06:48:09 +00:00
this . saslMechanism = null ;
2018-09-26 12:39:04 +00:00
}
private void sendBindRequest ( ) {
try {
mXmppConnectionService . restoredFromDatabaseLatch . await ( ) ;
} catch ( InterruptedException e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : interrupted while waiting for DB restore during bind " ) ;
2018-09-26 12:39:04 +00:00
return ;
}
clearIqCallbacks ( ) ;
if ( account . getJid ( ) . isBareJid ( ) ) {
account . setResource ( this . createNewResource ( ) ) ;
} else {
fixResource ( mXmppConnectionService , account ) ;
}
final IqPacket iq = new IqPacket ( IqPacket . TYPE . SET ) ;
2022-09-03 10:16:06 +00:00
final String resource =
Config . USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId ( ) : account . getResource ( ) ;
2018-09-26 12:39:04 +00:00
iq . addChild ( " bind " , Namespace . BIND ) . addChild ( " resource " ) . setContent ( resource ) ;
2022-09-03 10:16:06 +00:00
this . sendUnmodifiedIqPacket (
iq ,
( account , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
return ;
}
final Element bind = packet . findChild ( " bind " ) ;
if ( bind ! = null & & packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
isBound = true ;
final Element jid = bind . findChild ( " jid " ) ;
if ( jid ! = null & & jid . getContent ( ) ! = null ) {
try {
Jid assignedJid = Jid . ofEscaped ( jid . getContent ( ) ) ;
if ( ! account . getJid ( ) . getDomain ( ) . equals ( assignedJid . getDomain ( ) ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server tried to re-assign domain to "
+ assignedJid . getDomain ( ) ) ;
throw new StateChangingError ( Account . State . BIND_FAILURE ) ;
}
if ( account . setJid ( assignedJid ) ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : jid changed during bind. updating database " ) ;
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
}
if ( streamFeatures . hasChild ( " session " )
& & ! streamFeatures
. findChild ( " session " )
. hasChild ( " optional " ) ) {
sendStartSession ( ) ;
} else {
2022-09-03 18:17:29 +00:00
final boolean waitForDisco = enableStreamManagement ( ) ;
sendPostBindInitialization ( waitForDisco , false ) ;
2022-09-03 10:16:06 +00:00
}
return ;
} catch ( final IllegalArgumentException e ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : server reported invalid jid ( "
+ jid . getContent ( )
+ " ) on bind " ) ;
}
2018-09-26 12:39:04 +00:00
} else {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( )
+ " : disconnecting because of bind failure. (no jid) " ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
} else {
Log . d (
Config . LOGTAG ,
account . getJid ( )
+ " : disconnecting because of bind failure ( "
+ packet ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
final Element error = packet . findChild ( " error " ) ;
if ( packet . getType ( ) = = IqPacket . TYPE . ERROR
& & error ! = null
& & error . hasChild ( " conflict " ) ) {
account . setResource ( createNewResource ( ) ) ;
}
throw new StateChangingError ( Account . State . BIND_FAILURE ) ;
} ,
true ) ;
2018-09-26 12:39:04 +00:00
}
private void clearIqCallbacks ( ) {
final IqPacket failurePacket = new IqPacket ( IqPacket . TYPE . TIMEOUT ) ;
final ArrayList < OnIqPacketReceived > callbacks = new ArrayList < > ( ) ;
synchronized ( this . packetCallbacks ) {
if ( this . packetCallbacks . size ( ) = = 0 ) {
return ;
}
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : clearing "
+ this . packetCallbacks . size ( )
+ " iq callbacks " ) ;
final Iterator < Pair < IqPacket , OnIqPacketReceived > > iterator =
this . packetCallbacks . values ( ) . iterator ( ) ;
2018-09-26 12:39:04 +00:00
while ( iterator . hasNext ( ) ) {
Pair < IqPacket , OnIqPacketReceived > entry = iterator . next ( ) ;
callbacks . add ( entry . second ) ;
iterator . remove ( ) ;
}
}
for ( OnIqPacketReceived callback : callbacks ) {
try {
callback . onIqPacketReceived ( account , failurePacket ) ;
} catch ( StateChangingError error ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : caught StateChangingError( "
+ error . state . toString ( )
+ " ) while clearing callbacks " ) ;
// ignore
2018-09-26 12:39:04 +00:00
}
}
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : done clearing iq callbacks. "
+ this . packetCallbacks . size ( )
+ " left " ) ;
2018-09-26 12:39:04 +00:00
}
public void sendDiscoTimeout ( ) {
if ( mWaitForDisco . compareAndSet ( true , false ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : finalizing bind after disco timeout " ) ;
2018-09-26 12:39:04 +00:00
finalizeBind ( ) ;
}
}
private void sendStartSession ( ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : sending legacy session to outdated server " ) ;
2018-09-26 12:39:04 +00:00
final IqPacket startSession = new IqPacket ( IqPacket . TYPE . SET ) ;
startSession . addChild ( " session " , " urn:ietf:params:xml:ns:xmpp-session " ) ;
2022-09-03 10:16:06 +00:00
this . sendUnmodifiedIqPacket (
startSession ,
( account , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2022-09-03 18:17:29 +00:00
final boolean waitForDisco = enableStreamManagement ( ) ;
sendPostBindInitialization ( waitForDisco , false ) ;
2022-09-03 10:16:06 +00:00
} else if ( packet . getType ( ) ! = IqPacket . TYPE . TIMEOUT ) {
throw new StateChangingError ( Account . State . SESSION_FAILURE ) ;
}
} ,
true ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 18:17:29 +00:00
private boolean enableStreamManagement ( ) {
2022-09-03 10:16:06 +00:00
final boolean streamManagement =
this . streamFeatures . hasChild ( " sm " , Namespace . STREAM_MANAGEMENT ) ;
if ( streamManagement ) {
2018-09-26 12:39:04 +00:00
synchronized ( this . mStanzaQueue ) {
2022-09-03 10:16:06 +00:00
final EnablePacket enable = new EnablePacket ( ) ;
2018-09-26 12:39:04 +00:00
tagWriter . writeStanzaAsync ( enable ) ;
stanzasSent = 0 ;
mStanzaQueue . clear ( ) ;
}
2022-09-03 18:17:29 +00:00
return true ;
} else {
return false ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 18:17:29 +00:00
}
private void sendPostBindInitialization (
final boolean waitForDisco , final boolean carbonsEnabled ) {
features . carbonsEnabled = carbonsEnabled ;
2018-09-26 12:39:04 +00:00
features . blockListRequested = false ;
synchronized ( this . disco ) {
this . disco . clear ( ) ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : starting service discovery " ) ;
mPendingServiceDiscoveries . set ( 0 ) ;
2023-01-01 11:20:10 +00:00
mWaitForDisco . set ( waitForDisco ) ;
2018-09-26 12:39:04 +00:00
lastDiscoStarted = SystemClock . elapsedRealtime ( ) ;
2022-09-03 10:16:06 +00:00
mXmppConnectionService . scheduleWakeUpCall (
Config . CONNECT_DISCO_TIMEOUT , account . getUuid ( ) . hashCode ( ) ) ;
2022-10-14 11:13:21 +00:00
final Element caps = streamFeatures . findChild ( " c " ) ;
2018-09-26 12:39:04 +00:00
final String hash = caps = = null ? null : caps . getAttribute ( " hash " ) ;
final String ver = caps = = null ? null : caps . getAttribute ( " ver " ) ;
ServiceDiscoveryResult discoveryResult = null ;
if ( hash ! = null & & ver ! = null ) {
2022-09-03 10:16:06 +00:00
discoveryResult =
mXmppConnectionService . getCachedServiceDiscoveryResult ( new Pair < > ( hash , ver ) ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
final boolean requestDiscoItemsFirst =
! account . isOptionSet ( Account . OPTION_LOGGED_IN_SUCCESSFULLY ) ;
2018-09-26 12:39:04 +00:00
if ( requestDiscoItemsFirst ) {
2020-05-17 08:24:46 +00:00
sendServiceDiscoveryItems ( account . getDomain ( ) ) ;
2018-09-26 12:39:04 +00:00
}
if ( discoveryResult = = null ) {
2020-05-17 08:24:46 +00:00
sendServiceDiscoveryInfo ( account . getDomain ( ) ) ;
2018-09-26 12:39:04 +00:00
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : server caps came from cache " ) ;
2020-05-17 08:24:46 +00:00
disco . put ( account . getDomain ( ) , discoveryResult ) ;
2018-09-26 12:39:04 +00:00
}
2018-12-05 18:11:40 +00:00
discoverMamPreferences ( ) ;
2018-09-26 12:39:04 +00:00
sendServiceDiscoveryInfo ( account . getJid ( ) . asBareJid ( ) ) ;
if ( ! requestDiscoItemsFirst ) {
2020-05-17 08:24:46 +00:00
sendServiceDiscoveryItems ( account . getDomain ( ) ) ;
2018-09-26 12:39:04 +00:00
}
if ( ! mWaitForDisco . get ( ) ) {
finalizeBind ( ) ;
}
this . lastSessionStarted = SystemClock . elapsedRealtime ( ) ;
}
private void sendServiceDiscoveryInfo ( final Jid jid ) {
mPendingServiceDiscoveries . incrementAndGet ( ) ;
final IqPacket iq = new IqPacket ( IqPacket . TYPE . GET ) ;
iq . setTo ( jid ) ;
iq . query ( " http://jabber.org/protocol/disco#info " ) ;
2022-09-03 10:16:06 +00:00
this . sendIqPacket (
iq ,
( account , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
boolean advancedStreamFeaturesLoaded ;
synchronized ( XmppConnection . this . disco ) {
ServiceDiscoveryResult result = new ServiceDiscoveryResult ( packet ) ;
if ( jid . equals ( account . getDomain ( ) ) ) {
mXmppConnectionService . databaseBackend . insertDiscoveryResult (
result ) ;
}
disco . put ( jid , result ) ;
advancedStreamFeaturesLoaded =
disco . containsKey ( account . getDomain ( ) )
& & disco . containsKey ( account . getJid ( ) . asBareJid ( ) ) ;
}
if ( advancedStreamFeaturesLoaded
& & ( jid . equals ( account . getDomain ( ) )
| | jid . equals ( account . getJid ( ) . asBareJid ( ) ) ) ) {
enableAdvancedStreamFeatures ( ) ;
}
} else if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : could not query disco info for "
+ jid . toString ( ) ) ;
final boolean serverOrAccount =
jid . equals ( account . getDomain ( ) )
| | jid . equals ( account . getJid ( ) . asBareJid ( ) ) ;
final boolean advancedStreamFeaturesLoaded ;
if ( serverOrAccount ) {
synchronized ( XmppConnection . this . disco ) {
disco . put ( jid , ServiceDiscoveryResult . empty ( ) ) ;
advancedStreamFeaturesLoaded =
disco . containsKey ( account . getDomain ( ) )
& & disco . containsKey ( account . getJid ( ) . asBareJid ( ) ) ;
}
} else {
advancedStreamFeaturesLoaded = false ;
}
if ( advancedStreamFeaturesLoaded ) {
enableAdvancedStreamFeatures ( ) ;
}
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
if ( packet . getType ( ) ! = IqPacket . TYPE . TIMEOUT ) {
if ( mPendingServiceDiscoveries . decrementAndGet ( ) = = 0
& & mWaitForDisco . compareAndSet ( true , false ) ) {
finalizeBind ( ) ;
}
2019-08-13 19:18:32 +00:00
}
2022-09-03 10:16:06 +00:00
} ) ;
2018-09-26 12:39:04 +00:00
}
2018-12-05 18:11:40 +00:00
private void discoverMamPreferences ( ) {
IqPacket request = new IqPacket ( IqPacket . TYPE . GET ) ;
request . addChild ( " prefs " , MessageArchiveService . Version . MAM_2 . namespace ) ;
2022-09-03 10:16:06 +00:00
sendIqPacket (
request ,
( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
Element prefs =
response . findChild (
" prefs " , MessageArchiveService . Version . MAM_2 . namespace ) ;
isMamPreferenceAlways =
" always "
. equals (
prefs = = null
? null
: prefs . getAttribute ( " default " ) ) ;
}
} ) ;
2018-12-05 18:11:40 +00:00
}
2020-12-01 19:31:30 +00:00
private void discoverCommands ( ) {
final IqPacket request = new IqPacket ( IqPacket . TYPE . GET ) ;
request . setTo ( account . getDomain ( ) ) ;
request . addChild ( " query " , Namespace . DISCO_ITEMS ) . setAttribute ( " node " , Namespace . COMMANDS ) ;
2022-09-03 10:16:06 +00:00
sendIqPacket (
request ,
( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
final Element query = response . findChild ( " query " , Namespace . DISCO_ITEMS ) ;
if ( query = = null ) {
return ;
}
final HashMap < String , Jid > commands = new HashMap < > ( ) ;
for ( final Element child : query . getChildren ( ) ) {
if ( " item " . equals ( child . getName ( ) ) ) {
final String node = child . getAttribute ( " node " ) ;
final Jid jid = child . getAttributeAsJid ( " jid " ) ;
if ( node ! = null & & jid ! = null ) {
commands . put ( node , jid ) ;
}
}
}
synchronized ( this . commands ) {
this . commands . clear ( ) ;
this . commands . putAll ( commands ) ;
2020-12-31 08:32:05 +00:00
}
}
2022-09-03 10:16:06 +00:00
} ) ;
2020-12-01 19:31:30 +00:00
}
2018-12-05 18:11:40 +00:00
public boolean isMamPreferenceAlways ( ) {
return isMamPreferenceAlways ;
}
2018-09-26 12:39:04 +00:00
private void finalizeBind ( ) {
if ( bindListener ! = null ) {
bindListener . onBind ( account ) ;
}
2022-09-26 05:53:48 +00:00
changeStatusToOnline ( ) ;
2018-09-26 12:39:04 +00:00
}
private void enableAdvancedStreamFeatures ( ) {
if ( getFeatures ( ) . blocking ( ) & & ! features . blockListRequested ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : Requesting block list " ) ;
2022-09-03 10:16:06 +00:00
this . sendIqPacket (
getIqGenerator ( ) . generateGetBlockList ( ) , mXmppConnectionService . getIqParser ( ) ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
for ( final OnAdvancedStreamFeaturesLoaded listener :
advancedStreamFeaturesLoadedListeners ) {
2018-09-26 12:39:04 +00:00
listener . onAdvancedStreamFeaturesAvailable ( account ) ;
}
2019-08-22 07:57:39 +00:00
if ( getFeatures ( ) . carbons ( ) & & ! features . carbonsEnabled ) {
sendEnableCarbons ( ) ;
}
2020-12-01 19:31:30 +00:00
if ( getFeatures ( ) . commands ( ) ) {
discoverCommands ( ) ;
}
2018-09-26 12:39:04 +00:00
}
private void sendServiceDiscoveryItems ( final Jid server ) {
mPendingServiceDiscoveries . incrementAndGet ( ) ;
final IqPacket iq = new IqPacket ( IqPacket . TYPE . GET ) ;
2020-05-18 07:14:57 +00:00
iq . setTo ( server . getDomain ( ) ) ;
2018-09-26 12:39:04 +00:00
iq . query ( " http://jabber.org/protocol/disco#items " ) ;
2022-09-03 10:16:06 +00:00
this . sendIqPacket (
iq ,
( account , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
final HashSet < Jid > items = new HashSet < > ( ) ;
final List < Element > elements = packet . query ( ) . getChildren ( ) ;
for ( final Element element : elements ) {
if ( element . getName ( ) . equals ( " item " ) ) {
final Jid jid =
InvalidJid . getNullForInvalid (
element . getAttributeAsJid ( " jid " ) ) ;
if ( jid ! = null & & ! jid . equals ( account . getDomain ( ) ) ) {
items . add ( jid ) ;
}
}
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
for ( Jid jid : items ) {
sendServiceDiscoveryInfo ( jid ) ;
}
} else {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : could not query disco items of "
+ server ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
if ( packet . getType ( ) ! = IqPacket . TYPE . TIMEOUT ) {
if ( mPendingServiceDiscoveries . decrementAndGet ( ) = = 0
& & mWaitForDisco . compareAndSet ( true , false ) ) {
finalizeBind ( ) ;
}
}
} ) ;
2018-09-26 12:39:04 +00:00
}
private void sendEnableCarbons ( ) {
final IqPacket iq = new IqPacket ( IqPacket . TYPE . SET ) ;
2022-09-03 18:17:29 +00:00
iq . addChild ( " enable " , Namespace . CARBONS ) ;
2022-09-03 10:16:06 +00:00
this . sendIqPacket (
iq ,
( account , packet ) - > {
2022-09-03 18:17:29 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : successfully enabled carbons " ) ;
features . carbonsEnabled = true ;
} else {
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : could not enable carbons "
+ packet ) ;
}
} ) ;
2018-09-26 12:39:04 +00:00
}
2020-12-31 09:27:06 +00:00
private void processStreamError ( final Tag currentTag ) throws IOException {
2018-09-26 12:39:04 +00:00
final Element streamError = tagReader . readElement ( currentTag ) ;
if ( streamError = = null ) {
return ;
}
if ( streamError . hasChild ( " conflict " ) ) {
account . setResource ( createNewResource ( ) ) ;
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : switching resource due to conflict ( "
+ account . getResource ( )
+ " ) " ) ;
2018-09-26 12:39:04 +00:00
throw new IOException ( ) ;
} else if ( streamError . hasChild ( " host-unknown " ) ) {
throw new StateChangingException ( Account . State . HOST_UNKNOWN ) ;
2019-08-14 16:44:57 +00:00
} else if ( streamError . hasChild ( " policy-violation " ) ) {
2019-08-16 13:00:26 +00:00
this . lastConnect = SystemClock . elapsedRealtime ( ) ;
2018-12-18 10:02:25 +00:00
final String text = streamError . findChildContent ( " text " ) ;
2020-05-22 16:23:53 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : policy violation. " + text ) ;
failPendingMessages ( text ) ;
2018-09-26 12:39:04 +00:00
throw new StateChangingException ( Account . State . POLICY_VIOLATION ) ;
2023-10-13 06:29:23 +00:00
} else if ( streamError . hasChild ( " see-other-host " ) ) {
final String seeOtherHost = streamError . findChildContent ( " see-other-host " ) ;
final Resolver . Result currentResolverResult = this . currentResolverResult ;
if ( Strings . isNullOrEmpty ( seeOtherHost ) | | currentResolverResult = = null ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : stream error " + streamError ) ;
throw new StateChangingException ( Account . State . STREAM_ERROR ) ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : see other host: " + seeOtherHost + " " + currentResolverResult ) ;
final Resolver . Result seeOtherResult = currentResolverResult . seeOtherHost ( seeOtherHost ) ;
if ( seeOtherResult ! = null ) {
this . seeOtherHostResolverResult = seeOtherResult ;
throw new StateChangingException ( Account . State . SEE_OTHER_HOST ) ;
} else {
throw new StateChangingException ( Account . State . STREAM_ERROR ) ;
}
2018-09-26 12:39:04 +00:00
} else {
2022-08-29 16:40:49 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : stream error " + streamError ) ;
2018-09-26 12:39:04 +00:00
throw new StateChangingException ( Account . State . STREAM_ERROR ) ;
}
}
2020-05-22 16:23:53 +00:00
private void failPendingMessages ( final String error ) {
synchronized ( this . mStanzaQueue ) {
for ( int i = 0 ; i < mStanzaQueue . size ( ) ; + + i ) {
final AbstractAcknowledgeableStanza stanza = mStanzaQueue . valueAt ( i ) ;
if ( stanza instanceof MessagePacket ) {
final MessagePacket packet = ( MessagePacket ) stanza ;
final String id = packet . getId ( ) ;
final Jid to = packet . getTo ( ) ;
2022-09-03 10:16:06 +00:00
mXmppConnectionService . markMessage (
account , to . asBareJid ( ) , id , Message . STATUS_SEND_FAILED , error ) ;
2020-05-22 16:23:53 +00:00
}
}
}
}
2022-10-14 18:00:36 +00:00
private boolean establishStream ( final SSLSockets . Version sslVersion )
throws IOException , InterruptedException {
final boolean secureConnection = sslVersion ! = SSLSockets . Version . NONE ;
2023-10-23 12:40:40 +00:00
final SaslMechanism quickStartMechanism ;
if ( secureConnection ) {
quickStartMechanism = SaslMechanism . ensureAvailable ( account . getQuickStartMechanism ( ) , sslVersion ) ;
} else {
quickStartMechanism = null ;
}
2022-09-25 12:13:04 +00:00
if ( secureConnection
2022-10-14 18:00:36 +00:00
& & Config . QUICKSTART_ENABLED
2022-10-15 16:56:31 +00:00
& & quickStartMechanism ! = null
2022-09-25 12:13:04 +00:00
& & account . isOptionSet ( Account . OPTION_QUICKSTART_AVAILABLE ) ) {
2022-09-26 07:47:53 +00:00
mXmppConnectionService . restoredFromDatabaseLatch . await ( ) ;
2022-10-15 16:56:31 +00:00
this . saslMechanism = quickStartMechanism ;
final boolean usingFast = quickStartMechanism instanceof HashedToken ;
2022-09-25 12:13:04 +00:00
final Element authenticate =
2022-10-15 18:53:59 +00:00
generateAuthenticationRequest ( quickStartMechanism . getClientFirstMessage ( sslSocketOrNull ( this . socket ) ) , usingFast ) ;
2022-10-15 16:56:31 +00:00
authenticate . setAttribute ( " mechanism " , quickStartMechanism . getMechanism ( ) ) ;
2022-10-14 18:00:36 +00:00
sendStartStream ( true , false ) ;
2022-12-30 11:09:16 +00:00
synchronized ( this . mStanzaQueue ) {
this . stanzasSentBeforeAuthentication = this . stanzasSent ;
tagWriter . writeElement ( authenticate ) ;
}
2022-09-25 12:13:04 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . toString ( )
+ " : quick start with "
2022-10-15 16:56:31 +00:00
+ quickStartMechanism . getMechanism ( ) ) ;
2022-09-25 12:13:04 +00:00
return true ;
} else {
2022-10-14 18:00:36 +00:00
sendStartStream ( secureConnection , true ) ;
2022-09-25 12:13:04 +00:00
return false ;
}
}
2022-10-14 18:00:36 +00:00
private void sendStartStream ( final boolean from , final boolean flush ) throws IOException {
2018-09-26 12:39:04 +00:00
final Tag stream = Tag . start ( " stream:stream " ) ;
stream . setAttribute ( " to " , account . getServer ( ) ) ;
2022-10-14 18:00:36 +00:00
if ( from ) {
stream . setAttribute ( " from " , account . getJid ( ) . asBareJid ( ) . toEscapedString ( ) ) ;
}
2018-09-26 12:39:04 +00:00
stream . setAttribute ( " version " , " 1.0 " ) ;
2019-09-12 08:12:47 +00:00
stream . setAttribute ( " xml:lang " , LocalizedContent . STREAM_LANGUAGE ) ;
2018-09-26 12:39:04 +00:00
stream . setAttribute ( " xmlns " , " jabber:client " ) ;
2022-09-25 12:13:04 +00:00
stream . setAttribute ( " xmlns:stream " , Namespace . STREAMS ) ;
tagWriter . writeTag ( stream , flush ) ;
2018-09-26 12:39:04 +00:00
}
private String createNewResource ( ) {
return mXmppConnectionService . getString ( R . string . app_name ) + '.' + nextRandomId ( true ) ;
}
private String nextRandomId ( ) {
return nextRandomId ( false ) ;
}
2022-09-06 07:25:09 +00:00
private String nextRandomId ( final boolean s ) {
return CryptoHelper . random ( s ? 3 : 9 ) ;
2018-09-26 12:39:04 +00:00
}
public String sendIqPacket ( final IqPacket packet , final OnIqPacketReceived callback ) {
packet . setFrom ( account . getJid ( ) ) ;
return this . sendUnmodifiedIqPacket ( packet , callback , false ) ;
}
2022-09-03 10:16:06 +00:00
public synchronized String sendUnmodifiedIqPacket (
final IqPacket packet , final OnIqPacketReceived callback , boolean force ) {
2018-09-26 12:39:04 +00:00
if ( packet . getId ( ) = = null ) {
packet . setAttribute ( " id " , nextRandomId ( ) ) ;
}
if ( callback ! = null ) {
synchronized ( this . packetCallbacks ) {
packetCallbacks . put ( packet . getId ( ) , new Pair < > ( packet , callback ) ) ;
}
}
this . sendPacket ( packet , force ) ;
return packet . getId ( ) ;
}
public void sendMessagePacket ( final MessagePacket packet ) {
this . sendPacket ( packet ) ;
}
public void sendPresencePacket ( final PresencePacket packet ) {
this . sendPacket ( packet ) ;
}
private synchronized void sendPacket ( final AbstractStanza packet ) {
sendPacket ( packet , false ) ;
}
private synchronized void sendPacket ( final AbstractStanza packet , final boolean force ) {
if ( stanzasSent = = Integer . MAX_VALUE ) {
resetStreamId ( ) ;
disconnect ( true ) ;
return ;
}
synchronized ( this . mStanzaQueue ) {
if ( force | | isBound ) {
tagWriter . writeStanzaAsync ( packet ) ;
} else {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " do not write stanza to unbound stream "
+ packet . toString ( ) ) ;
2018-09-26 12:39:04 +00:00
}
if ( packet instanceof AbstractAcknowledgeableStanza ) {
AbstractAcknowledgeableStanza stanza = ( AbstractAcknowledgeableStanza ) packet ;
if ( this . mStanzaQueue . size ( ) ! = 0 ) {
int currentHighestKey = this . mStanzaQueue . keyAt ( this . mStanzaQueue . size ( ) - 1 ) ;
if ( currentHighestKey ! = stanzasSent ) {
throw new AssertionError ( " Stanza count messed up " ) ;
}
}
+ + stanzasSent ;
2022-12-30 09:53:49 +00:00
if ( Config . EXTENDED_SM_LOGGING ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : counting outbound " + packet . getName ( ) + " as # " + stanzasSent ) ;
}
2018-09-26 12:39:04 +00:00
this . mStanzaQueue . append ( stanzasSent , stanza ) ;
if ( stanza instanceof MessagePacket & & stanza . getId ( ) ! = null & & inSmacksSession ) {
if ( Config . EXTENDED_SM_LOGGING ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : requesting ack for message stanza # "
+ stanzasSent ) ;
2018-09-26 12:39:04 +00:00
}
2022-09-03 10:16:06 +00:00
tagWriter . writeStanzaAsync ( new RequestPacket ( ) ) ;
2018-09-26 12:39:04 +00:00
}
}
}
}
public void sendPing ( ) {
if ( ! r ( ) ) {
final IqPacket iq = new IqPacket ( IqPacket . TYPE . GET ) ;
iq . setFrom ( account . getJid ( ) ) ;
2019-06-18 11:20:24 +00:00
iq . addChild ( " ping " , Namespace . PING ) ;
2018-09-26 12:39:04 +00:00
this . sendIqPacket ( iq , null ) ;
}
this . lastPingSent = SystemClock . elapsedRealtime ( ) ;
}
2022-09-03 10:16:06 +00:00
public void setOnMessagePacketReceivedListener ( final OnMessagePacketReceived listener ) {
2018-09-26 12:39:04 +00:00
this . messageListener = listener ;
}
2022-09-03 10:16:06 +00:00
public void setOnUnregisteredIqPacketReceivedListener ( final OnIqPacketReceived listener ) {
2018-09-26 12:39:04 +00:00
this . unregisteredIqListener = listener ;
}
2022-09-03 10:16:06 +00:00
public void setOnPresencePacketReceivedListener ( final OnPresencePacketReceived listener ) {
2018-09-26 12:39:04 +00:00
this . presenceListener = listener ;
}
2022-09-03 10:16:06 +00:00
public void setOnJinglePacketReceivedListener ( final OnJinglePacketReceived listener ) {
2018-09-26 12:39:04 +00:00
this . jingleListener = listener ;
}
public void setOnStatusChangedListener ( final OnStatusChanged listener ) {
this . statusListener = listener ;
}
public void setOnBindListener ( final OnBindListener listener ) {
this . bindListener = listener ;
}
public void setOnMessageAcknowledgeListener ( final OnMessageAcknowledged listener ) {
this . acknowledgedListener = listener ;
}
2022-09-03 10:16:06 +00:00
public void addOnAdvancedStreamFeaturesAvailableListener (
final OnAdvancedStreamFeaturesLoaded listener ) {
2018-09-26 12:39:04 +00:00
this . advancedStreamFeaturesLoadedListeners . add ( listener ) ;
}
private void forceCloseSocket ( ) {
2019-07-04 08:12:08 +00:00
FileBackend . close ( this . socket ) ;
FileBackend . close ( this . tagReader ) ;
2018-09-26 12:39:04 +00:00
}
public void interrupt ( ) {
if ( this . mThread ! = null ) {
this . mThread . interrupt ( ) ;
}
}
public void disconnect ( final boolean force ) {
interrupt ( ) ;
2019-07-04 08:12:08 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : disconnecting force= " + force ) ;
2018-09-26 12:39:04 +00:00
if ( force ) {
forceCloseSocket ( ) ;
} else {
final TagWriter currentTagWriter = this . tagWriter ;
if ( currentTagWriter . isActive ( ) ) {
currentTagWriter . finish ( ) ;
final Socket currentSocket = this . socket ;
final CountDownLatch streamCountDownLatch = this . mStreamCountDownLatch ;
try {
currentTagWriter . await ( 1 , TimeUnit . SECONDS ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : closing stream " ) ;
currentTagWriter . writeTag ( Tag . end ( " stream:stream " ) ) ;
if ( streamCountDownLatch ! = null ) {
if ( streamCountDownLatch . await ( 1 , TimeUnit . SECONDS ) ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( ) + " : remote ended stream " ) ;
2018-09-26 12:39:04 +00:00
} else {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : remote has not closed socket. force closing " ) ;
2018-09-26 12:39:04 +00:00
}
}
} catch ( InterruptedException e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : interrupted while gracefully closing stream " ) ;
2018-09-26 12:39:04 +00:00
} catch ( final IOException e ) {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : io exception during disconnect ( "
+ e . getMessage ( )
+ " ) " ) ;
2018-09-26 12:39:04 +00:00
} finally {
FileBackend . close ( currentSocket ) ;
}
} else {
forceCloseSocket ( ) ;
}
}
}
private void resetStreamId ( ) {
this . streamId = null ;
2023-05-02 13:35:27 +00:00
this . boundStreamFeatures = null ;
2018-09-26 12:39:04 +00:00
}
private List < Entry < Jid , ServiceDiscoveryResult > > findDiscoItemsByFeature ( final String feature ) {
synchronized ( this . disco ) {
final List < Entry < Jid , ServiceDiscoveryResult > > items = new ArrayList < > ( ) ;
for ( final Entry < Jid , ServiceDiscoveryResult > cursor : this . disco . entrySet ( ) ) {
if ( cursor . getValue ( ) . getFeatures ( ) . contains ( feature ) ) {
items . add ( cursor ) ;
}
}
return items ;
}
}
public Jid findDiscoItemByFeature ( final String feature ) {
final List < Entry < Jid , ServiceDiscoveryResult > > items = findDiscoItemsByFeature ( feature ) ;
if ( items . size ( ) > = 1 ) {
return items . get ( 0 ) . getKey ( ) ;
}
return null ;
}
public boolean r ( ) {
if ( getFeatures ( ) . sm ( ) ) {
2022-09-03 10:16:06 +00:00
this . tagWriter . writeStanzaAsync ( new RequestPacket ( ) ) ;
2018-09-26 12:39:04 +00:00
return true ;
} else {
return false ;
}
}
public List < String > getMucServersWithholdAccount ( ) {
2020-12-31 09:27:06 +00:00
final List < String > servers = getMucServers ( ) ;
servers . remove ( account . getDomain ( ) . toEscapedString ( ) ) ;
2018-09-26 12:39:04 +00:00
return servers ;
}
public List < String > getMucServers ( ) {
List < String > servers = new ArrayList < > ( ) ;
synchronized ( this . disco ) {
for ( final Entry < Jid , ServiceDiscoveryResult > cursor : disco . entrySet ( ) ) {
final ServiceDiscoveryResult value = cursor . getValue ( ) ;
if ( value . getFeatures ( ) . contains ( " http://jabber.org/protocol/muc " )
& & value . hasIdentity ( " conference " , " text " )
& & ! value . getFeatures ( ) . contains ( " jabber:iq:gateway " )
& & ! value . hasIdentity ( " conference " , " irc " ) ) {
servers . add ( cursor . getKey ( ) . toString ( ) ) ;
}
}
}
return servers ;
}
public String getMucServer ( ) {
List < String > servers = getMucServers ( ) ;
return servers . size ( ) > 0 ? servers . get ( 0 ) : null ;
}
2023-10-03 10:56:10 +00:00
public int getTimeToNextAttempt ( final boolean aggressive ) {
final int interval ;
if ( aggressive ) {
interval = Math . min ( ( int ) ( 3 * Math . pow ( 1 . 3 , attempt ) ) , 60 ) ;
} else {
final int additionalTime =
account . getLastErrorStatus ( ) = = Account . State . POLICY_VIOLATION ? 3 : 0 ;
interval = Math . min ( ( int ) ( 25 * Math . pow ( 1 . 3 , ( additionalTime + attempt ) ) ) , 300 ) ;
}
2022-09-03 10:16:06 +00:00
final int secondsSinceLast =
( int ) ( ( SystemClock . elapsedRealtime ( ) - this . lastConnect ) / 1000 ) ;
2018-09-26 12:39:04 +00:00
return interval - secondsSinceLast ;
}
public int getAttempt ( ) {
return this . attempt ;
}
public Features getFeatures ( ) {
return this . features ;
}
public long getLastSessionEstablished ( ) {
final long diff = SystemClock . elapsedRealtime ( ) - this . lastSessionStarted ;
return System . currentTimeMillis ( ) - diff ;
}
public long getLastConnect ( ) {
return this . lastConnect ;
}
public long getLastPingSent ( ) {
return this . lastPingSent ;
}
public long getLastDiscoStarted ( ) {
return this . lastDiscoStarted ;
}
public long getLastPacketReceived ( ) {
return this . lastPacketReceived ;
}
public void sendActive ( ) {
this . sendPacket ( new ActivePacket ( ) ) ;
}
public void sendInactive ( ) {
this . sendPacket ( new InactivePacket ( ) ) ;
}
public void resetAttemptCount ( boolean resetConnectTime ) {
this . attempt = 0 ;
if ( resetConnectTime ) {
this . lastConnect = 0 ;
}
}
public void setInteractive ( boolean interactive ) {
this . mInteractive = interactive ;
}
private IqGenerator getIqGenerator ( ) {
return mXmppConnectionService . getIqGenerator ( ) ;
}
private class MyKeyManager implements X509KeyManager {
@Override
public String chooseClientAlias ( String [ ] strings , Principal [ ] principals , Socket socket ) {
return account . getPrivateKeyAlias ( ) ;
}
@Override
public String chooseServerAlias ( String s , Principal [ ] principals , Socket socket ) {
return null ;
}
@Override
public X509Certificate [ ] getCertificateChain ( String alias ) {
Log . d ( Config . LOGTAG , " getting certificate chain " ) ;
try {
return KeyChain . getCertificateChain ( mXmppConnectionService , alias ) ;
2022-08-29 16:40:49 +00:00
} catch ( final Exception e ) {
Log . d ( Config . LOGTAG , " could not get certificate chain " , e ) ;
2018-09-26 12:39:04 +00:00
return new X509Certificate [ 0 ] ;
}
}
@Override
public String [ ] getClientAliases ( String s , Principal [ ] principals ) {
final String alias = account . getPrivateKeyAlias ( ) ;
2022-09-03 10:16:06 +00:00
return alias ! = null ? new String [ ] { alias } : new String [ 0 ] ;
2018-09-26 12:39:04 +00:00
}
@Override
public String [ ] getServerAliases ( String s , Principal [ ] principals ) {
return new String [ 0 ] ;
}
@Override
public PrivateKey getPrivateKey ( String alias ) {
try {
return KeyChain . getPrivateKey ( mXmppConnectionService , alias ) ;
} catch ( Exception e ) {
return null ;
}
}
}
2020-12-31 09:27:06 +00:00
private static class StateChangingError extends Error {
2018-09-26 12:39:04 +00:00
private final Account . State state ;
public StateChangingError ( Account . State state ) {
this . state = state ;
}
}
2020-12-31 09:27:06 +00:00
private static class StateChangingException extends IOException {
2018-09-26 12:39:04 +00:00
private final Account . State state ;
public StateChangingException ( Account . State state ) {
this . state = state ;
}
}
public class Features {
XmppConnection connection ;
private boolean carbonsEnabled = false ;
private boolean encryptionEnabled = false ;
private boolean blockListRequested = false ;
public Features ( final XmppConnection connection ) {
this . connection = connection ;
}
private boolean hasDiscoFeature ( final Jid server , final String feature ) {
synchronized ( XmppConnection . this . disco ) {
2022-09-04 07:28:00 +00:00
final ServiceDiscoveryResult sdr = connection . disco . get ( server ) ;
return sdr ! = null & & sdr . getFeatures ( ) . contains ( feature ) ;
2018-09-26 12:39:04 +00:00
}
}
public boolean carbons ( ) {
2022-09-03 18:17:29 +00:00
return hasDiscoFeature ( account . getDomain ( ) , Namespace . CARBONS ) ;
2018-09-26 12:39:04 +00:00
}
2020-12-01 19:31:30 +00:00
public boolean commands ( ) {
return hasDiscoFeature ( account . getDomain ( ) , Namespace . COMMANDS ) ;
}
public boolean easyOnboardingInvites ( ) {
synchronized ( commands ) {
return commands . containsKey ( Namespace . EASY_ONBOARDING_INVITE ) ;
}
}
2018-09-26 12:39:04 +00:00
public boolean bookmarksConversion ( ) {
2022-09-03 10:16:06 +00:00
return hasDiscoFeature ( account . getJid ( ) . asBareJid ( ) , Namespace . BOOKMARKS_CONVERSION )
& & pepPublishOptions ( ) ;
2018-09-26 12:39:04 +00:00
}
2019-01-17 16:55:47 +00:00
public boolean avatarConversion ( ) {
2022-09-03 10:16:06 +00:00
return hasDiscoFeature ( account . getJid ( ) . asBareJid ( ) , Namespace . AVATAR_CONVERSION )
& & pepPublishOptions ( ) ;
2019-01-17 16:55:47 +00:00
}
2018-09-26 12:39:04 +00:00
public boolean blocking ( ) {
2020-05-17 08:24:46 +00:00
return hasDiscoFeature ( account . getDomain ( ) , Namespace . BLOCKING ) ;
2018-09-26 12:39:04 +00:00
}
public boolean spamReporting ( ) {
2023-11-12 15:11:11 +00:00
return hasDiscoFeature ( account . getDomain ( ) , Namespace . REPORTING ) ;
2018-09-26 12:39:04 +00:00
}
public boolean flexibleOfflineMessageRetrieval ( ) {
2022-09-03 10:16:06 +00:00
return hasDiscoFeature (
account . getDomain ( ) , Namespace . FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL ) ;
2018-09-26 12:39:04 +00:00
}
public boolean register ( ) {
2020-05-17 08:24:46 +00:00
return hasDiscoFeature ( account . getDomain ( ) , Namespace . REGISTER ) ;
2018-09-26 12:39:04 +00:00
}
2020-01-09 13:13:05 +00:00
public boolean invite ( ) {
2022-09-03 10:16:06 +00:00
return connection . streamFeatures ! = null
& & connection . streamFeatures . hasChild ( " register " , Namespace . INVITE ) ;
2020-01-09 13:13:05 +00:00
}
2018-09-26 12:39:04 +00:00
public boolean sm ( ) {
return streamId ! = null
2022-09-03 10:16:06 +00:00
| | ( connection . streamFeatures ! = null
2022-12-16 07:07:41 +00:00
& & connection . streamFeatures . hasChild ( " sm " , Namespace . STREAM_MANAGEMENT ) ) ;
2018-09-26 12:39:04 +00:00
}
public boolean csi ( ) {
2022-09-03 10:16:06 +00:00
return connection . streamFeatures ! = null
& & connection . streamFeatures . hasChild ( " csi " , Namespace . CSI ) ;
2018-09-26 12:39:04 +00:00
}
public boolean pep ( ) {
synchronized ( XmppConnection . this . disco ) {
ServiceDiscoveryResult info = disco . get ( account . getJid ( ) . asBareJid ( ) ) ;
return info ! = null & & info . hasIdentity ( " pubsub " , " pep " ) ;
}
}
public boolean pepPersistent ( ) {
synchronized ( XmppConnection . this . disco ) {
ServiceDiscoveryResult info = disco . get ( account . getJid ( ) . asBareJid ( ) ) ;
2022-09-03 10:16:06 +00:00
return info ! = null
& & info . getFeatures ( )
. contains ( " http://jabber.org/protocol/pubsub#persistent-items " ) ;
2018-09-26 12:39:04 +00:00
}
}
public boolean pepPublishOptions ( ) {
return hasDiscoFeature ( account . getJid ( ) . asBareJid ( ) , Namespace . PUBSUB_PUBLISH_OPTIONS ) ;
}
public boolean pepOmemoWhitelisted ( ) {
2022-09-03 10:16:06 +00:00
return hasDiscoFeature (
account . getJid ( ) . asBareJid ( ) , AxolotlService . PEP_OMEMO_WHITELISTED ) ;
2018-09-26 12:39:04 +00:00
}
public boolean mam ( ) {
return MessageArchiveService . Version . has ( getAccountFeatures ( ) ) ;
}
public List < String > getAccountFeatures ( ) {
ServiceDiscoveryResult result = connection . disco . get ( account . getJid ( ) . asBareJid ( ) ) ;
return result = = null ? Collections . emptyList ( ) : result . getFeatures ( ) ;
}
public boolean push ( ) {
2019-06-24 16:16:03 +00:00
return hasDiscoFeature ( account . getJid ( ) . asBareJid ( ) , Namespace . PUSH )
2020-05-17 08:24:46 +00:00
| | hasDiscoFeature ( account . getDomain ( ) , Namespace . PUSH ) ;
2018-09-26 12:39:04 +00:00
}
public boolean rosterVersioning ( ) {
return connection . streamFeatures ! = null & & connection . streamFeatures . hasChild ( " ver " ) ;
}
public void setBlockListRequested ( boolean value ) {
this . blockListRequested = value ;
}
public boolean httpUpload ( long filesize ) {
if ( Config . DISABLE_HTTP_UPLOAD ) {
return false ;
} else {
2022-09-03 10:16:06 +00:00
for ( String namespace :
new String [ ] { Namespace . HTTP_UPLOAD , Namespace . HTTP_UPLOAD_LEGACY } ) {
List < Entry < Jid , ServiceDiscoveryResult > > items =
findDiscoItemsByFeature ( namespace ) ;
2018-09-26 12:39:04 +00:00
if ( items . size ( ) > 0 ) {
try {
2022-09-03 10:16:06 +00:00
long maxsize =
Long . parseLong (
items . get ( 0 )
. getValue ( )
. getExtendedDiscoInformation (
namespace , " max-file-size " ) ) ;
2018-09-26 12:39:04 +00:00
if ( filesize < = maxsize ) {
return true ;
} else {
2022-09-03 10:16:06 +00:00
Log . d (
Config . LOGTAG ,
account . getJid ( ) . asBareJid ( )
+ " : http upload is not available for files with size "
+ filesize
+ " (max is "
+ maxsize
+ " ) " ) ;
2018-09-26 12:39:04 +00:00
return false ;
}
} catch ( Exception e ) {
return true ;
}
}
}
return false ;
}
}
public boolean useLegacyHttpUpload ( ) {
2022-09-03 10:16:06 +00:00
return findDiscoItemByFeature ( Namespace . HTTP_UPLOAD ) = = null
& & findDiscoItemByFeature ( Namespace . HTTP_UPLOAD_LEGACY ) ! = null ;
2018-09-26 12:39:04 +00:00
}
public long getMaxHttpUploadSize ( ) {
2022-09-03 10:16:06 +00:00
for ( String namespace :
new String [ ] { Namespace . HTTP_UPLOAD , Namespace . HTTP_UPLOAD_LEGACY } ) {
2018-09-26 12:39:04 +00:00
List < Entry < Jid , ServiceDiscoveryResult > > items = findDiscoItemsByFeature ( namespace ) ;
if ( items . size ( ) > 0 ) {
try {
2022-09-03 10:16:06 +00:00
return Long . parseLong (
items . get ( 0 )
. getValue ( )
. getExtendedDiscoInformation ( namespace , " max-file-size " ) ) ;
2018-09-26 12:39:04 +00:00
} catch ( Exception e ) {
2022-09-03 10:16:06 +00:00
// ignored
2018-09-26 12:39:04 +00:00
}
}
}
return - 1 ;
}
public boolean stanzaIds ( ) {
return hasDiscoFeature ( account . getJid ( ) . asBareJid ( ) , Namespace . STANZA_IDS ) ;
}
2019-09-27 23:21:19 +00:00
public boolean bookmarks2 ( ) {
2023-11-10 14:29:26 +00:00
return pepPublishOptions ( ) & & hasDiscoFeature ( account . getJid ( ) . asBareJid ( ) , Namespace . BOOKMARKS2_COMPAT ) ;
2019-09-27 23:21:19 +00:00
}
2020-04-08 15:52:47 +00:00
2020-04-21 09:40:05 +00:00
public boolean externalServiceDiscovery ( ) {
2020-05-22 16:23:53 +00:00
return hasDiscoFeature ( account . getDomain ( ) , Namespace . EXTERNAL_SERVICE_DISCOVERY ) ;
2020-04-08 15:52:47 +00:00
}
2018-09-26 12:39:04 +00:00
}
2014-01-30 15:42:35 +00:00
}