modify XmppConnection to change status to online for unbound cons

This commit is contained in:
Daniel Gultsch 2023-02-14 18:04:04 +01:00
parent a204bf9ec1
commit 35360fde91
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
9 changed files with 158 additions and 114 deletions

View file

@ -2,11 +2,11 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "219a451e9a1889222b7549c8b3c0a5b3", "identityHash": "1952101c2c0d439fcd6c9d417f126a54",
"entities": [ "entities": [
{ {
"tableName": "account", "tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `address` TEXT NOT NULL, `resource` TEXT, `randomSeed` BLOB, `enabled` INTEGER NOT NULL, `quickStartAvailable` INTEGER NOT NULL, `pendingRegistration` INTEGER NOT NULL, `loggedInSuccessfully` INTEGER NOT NULL, `showErrorNotification` INTEGER NOT NULL, `rosterVersion` TEXT, `hostname` TEXT, `port` INTEGER, `directTls` INTEGER, `proxytype` TEXT, `proxyhostname` TEXT, `proxyport` INTEGER)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `address` TEXT NOT NULL, `resource` TEXT, `randomSeed` BLOB, `enabled` INTEGER NOT NULL, `quickStartAvailable` INTEGER NOT NULL, `loginAndBind` INTEGER NOT NULL, `showErrorNotification` INTEGER NOT NULL, `rosterVersion` TEXT, `hostname` TEXT, `port` INTEGER, `directTls` INTEGER, `proxytype` TEXT, `proxyhostname` TEXT, `proxyport` INTEGER)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -45,14 +45,8 @@
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "pendingRegistration", "fieldPath": "loginAndBind",
"columnName": "pendingRegistration", "columnName": "loginAndBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "loggedInSuccessfully",
"columnName": "loggedInSuccessfully",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
@ -2266,7 +2260,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '219a451e9a1889222b7549c8b3c0a5b3')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1952101c2c0d439fcd6c9d417f126a54')"
] ]
} }
} }

View file

@ -16,6 +16,7 @@ public final class Namespace {
public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat"; public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat";
public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0"; public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0";
public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams";
public static final String CAPTCHA = "urn:xmpp:captcha";
public static final String CARBONS = "urn:xmpp:carbons:2"; public static final String CARBONS = "urn:xmpp:carbons:2";
public static final String CHANNEL_BINDING = "urn:xmpp:sasl-cb:0"; public static final String CHANNEL_BINDING = "urn:xmpp:sasl-cb:0";
public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0"; public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0";

View file

@ -2,6 +2,7 @@ package im.conversations.android.database.dao;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query; import androidx.room.Query;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
@ -13,7 +14,7 @@ import java.util.List;
@Dao @Dao
public interface AccountDao { public interface AccountDao {
@Insert @Insert(onConflict = OnConflictStrategy.REPLACE)
long insert(final AccountEntity account); long insert(final AccountEntity account);
@Query("SELECT id,address,randomSeed FROM account WHERE enabled = 1") @Query("SELECT id,address,randomSeed FROM account WHERE enabled = 1")
@ -37,11 +38,8 @@ public interface AccountDao {
@Query("SELECT quickStartAvailable FROM account where id=:id") @Query("SELECT quickStartAvailable FROM account where id=:id")
boolean quickStartAvailable(long id); boolean quickStartAvailable(long id);
@Query("SELECT pendingRegistration FROM account where id=:id") @Query("SELECT loginAndBind FROM account where id=:id")
boolean pendingRegistration(long id); boolean loginAndBind(long id);
@Query("SELECT loggedInSuccessfully == 0 FROM account where id=:id")
boolean isInitialLogin(long id);
@Query( @Query(
"UPDATE account set quickStartAvailable=:available WHERE id=:id AND" "UPDATE account set quickStartAvailable=:available WHERE id=:id AND"
@ -49,14 +47,9 @@ public interface AccountDao {
void setQuickStartAvailable(long id, boolean available); void setQuickStartAvailable(long id, boolean available);
@Query( @Query(
"UPDATE account set pendingRegistration=:pendingRegistration WHERE id=:id AND" "UPDATE account set loginAndBind=:loginAndBind WHERE id=:id AND"
+ " pendingRegistration != :pendingRegistration") + " loginAndBind != :loginAndBind")
void setPendingRegistration(long id, boolean pendingRegistration); void setLoginAndBind(long id, boolean loginAndBind);
@Query(
"UPDATE account set loggedInSuccessfully=:loggedInSuccessfully WHERE id=:id AND"
+ " loggedInSuccessfully != :loggedInSuccessfully")
int setLoggedInSuccessfully(long id, boolean loggedInSuccessfully);
@Query( @Query(
"UPDATE account set showErrorNotification=:showErrorNotification WHERE id=:id AND" "UPDATE account set showErrorNotification=:showErrorNotification WHERE id=:id AND"

View file

@ -28,11 +28,7 @@ public class AccountEntity {
public boolean enabled; public boolean enabled;
public boolean quickStartAvailable = false; public boolean quickStartAvailable = false;
public boolean pendingRegistration = false; public boolean loginAndBind = true;
// TODO this is only used during setup; depending on how the setup procedure will look in the
// future we might get rid of this property
public boolean loggedInSuccessfully = false;
public boolean showErrorNotification = true; public boolean showErrorNotification = true;

View file

@ -5,12 +5,15 @@ import androidx.annotation.NonNull;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.IDs; import im.conversations.android.IDs;
import im.conversations.android.database.CredentialStore; import im.conversations.android.database.CredentialStore;
import im.conversations.android.database.entity.AccountEntity; import im.conversations.android.database.entity.AccountEntity;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.ConnectionPool; import im.conversations.android.xmpp.ConnectionPool;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.manager.RegistrationManager;
public class AccountRepository extends AbstractRepository { public class AccountRepository extends AbstractRepository {
@ -18,7 +21,8 @@ public class AccountRepository extends AbstractRepository {
super(context); super(context);
} }
private Account createAccount(@NonNull final Jid address, final String password) { private Account createAccount(
@NonNull final Jid address, final String password, final boolean loginAndBind) {
Preconditions.checkArgument( Preconditions.checkArgument(
address.isBareJid(), "Account should be specified without resource"); address.isBareJid(), "Account should be specified without resource");
Preconditions.checkArgument(password != null, "Missing password"); Preconditions.checkArgument(password != null, "Missing password");
@ -26,6 +30,7 @@ public class AccountRepository extends AbstractRepository {
final var entity = new AccountEntity(); final var entity = new AccountEntity();
entity.address = address; entity.address = address;
entity.enabled = true; entity.enabled = true;
entity.loginAndBind = loginAndBind;
entity.randomSeed = randomSeed; entity.randomSeed = randomSeed;
final long id = database.accountDao().insert(entity); final long id = database.accountDao().insert(entity);
final var account = new Account(id, address, entity.randomSeed); final var account = new Account(id, address, entity.randomSeed);
@ -38,8 +43,23 @@ public class AccountRepository extends AbstractRepository {
return account; return account;
} }
public ListenableFuture<Account> createAccountAsync(
final @NonNull Jid address, final String password, final boolean loginAndBind) {
return Futures.submit(() -> createAccount(address, password, loginAndBind), IO_EXECUTOR);
}
public ListenableFuture<Account> createAccountAsync( public ListenableFuture<Account> createAccountAsync(
final @NonNull Jid address, final String password) { final @NonNull Jid address, final String password) {
return Futures.submit(() -> createAccount(address, password), IO_EXECUTOR); return createAccountAsync(address, password, true);
}
public ListenableFuture<RegistrationManager.Registration> getRegistration(
final Account account) {
final ListenableFuture<XmppConnection> connectedFuture =
ConnectionPool.getInstance(context).reconfigure(account).asConnectedFuture();
return Futures.transformAsync(
connectedFuture,
xc -> xc.getManager(RegistrationManager.class).getRegistration(),
MoreExecutors.directExecutor());
} }
} }

View file

@ -118,10 +118,14 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
public class XmppConnection implements Runnable { public class XmppConnection implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(XmppConnection.class);
protected final Account account; protected final Account account;
private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>(); private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>();
private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>(); private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>();
@ -289,9 +293,9 @@ public class XmppConnection implements Runnable {
try { try {
Socket localSocket; Socket localSocket;
shouldAuthenticate = shouldAuthenticate =
!ConversationsDatabase.getInstance(context) ConversationsDatabase.getInstance(context)
.accountDao() .accountDao()
.pendingRegistration(account.id); .loginAndBind(account.id);
this.changeStatus(ConnectionState.CONNECTING); this.changeStatus(ConnectionState.CONNECTING);
// TODO introduce proxy check // TODO introduce proxy check
final boolean useTor = /*fcontext.useTorToConnect() ||*/ account.isOnion(); final boolean useTor = /*fcontext.useTorToConnect() ||*/ account.isOnion();
@ -1177,7 +1181,7 @@ public class XmppConnection implements Runnable {
final SSLSocket sslSocket = upgradeSocketToTls(socket); final SSLSocket sslSocket = upgradeSocketToTls(socket);
tagReader.setInputStream(sslSocket.getInputStream()); tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream());
Log.d(Config.LOGTAG, account.address + ": TLS connection established"); LOGGER.info("TLS connection established");
final boolean quickStart; final boolean quickStart;
try { try {
quickStart = establishStream(SSLSockets.version(sslSocket)); quickStart = establishStream(SSLSockets.version(sslSocket));
@ -1234,13 +1238,10 @@ public class XmppConnection implements Runnable {
} }
private void processStreamFeatures(final Tag currentTag) throws IOException { private void processStreamFeatures(final Tag currentTag) throws IOException {
final boolean pendingRegistration = final boolean loginAndBind =
ConversationsDatabase.getInstance(context) ConversationsDatabase.getInstance(context).accountDao().loginAndBind(account.id);
.accountDao()
.pendingRegistration(account.id);
this.streamFeatures = tagReader.readElement(currentTag, Features.class); this.streamFeatures = tagReader.readElement(currentTag, Features.class);
final boolean isSecure = isSecure(); final boolean needsBinding = !isBound && loginAndBind;
final boolean needsBinding = !isBound && !pendingRegistration;
if (this.quickStartInProgress) { if (this.quickStartInProgress) {
if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) { if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) {
Log.d( Log.d(
@ -1271,29 +1272,23 @@ public class XmppConnection implements Runnable {
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER); throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
} }
if (this.streamFeatures.hasChild("starttls", Namespace.TLS) && !this.encryptionEnabled) { if (this.streamFeatures.hasChild("starttls", Namespace.TLS) && !this.encryptionEnabled) {
LOGGER.info("Negotiating TLS (STARTTLS)");
sendStartTLS(); sendStartTLS();
} else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE) return;
&& pendingRegistration) { } else if (!isSecure()) {
if (isSecure) { LOGGER.error("Server does not support STARTTLS");
register();
} else {
Log.d(
Config.LOGTAG,
account.address
+ ": unable to find STARTTLS for registration process "
+ XmlHelper.printElementNames(this.streamFeatures));
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER); throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
} }
} else if (!this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
&& pendingRegistration) { if (Boolean.FALSE.equals(loginAndBind)) {
throw new StateChangingException(ConnectionState.REGISTRATION_NOT_SUPPORTED); LOGGER.info("No login and bind required. Connection is considered online");
this.lastPacketReceived = SystemClock.elapsedRealtime();
this.changeStatus(ConnectionState.ONLINE);
} else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2) } else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)
&& shouldAuthenticate && shouldAuthenticate) {
&& isSecure) {
authenticate(SaslMechanism.Version.SASL_2); authenticate(SaslMechanism.Version.SASL_2);
} else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL) } else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL)
&& shouldAuthenticate && shouldAuthenticate) {
&& isSecure) {
authenticate(SaslMechanism.Version.SASL); authenticate(SaslMechanism.Version.SASL);
} else if (this.streamFeatures.streamManagement() && streamId != null && !inSmacksSession) { } else if (this.streamFeatures.streamManagement() && streamId != null && !inSmacksSession) {
if (Config.EXTENDED_SM_LOGGING) { if (Config.EXTENDED_SM_LOGGING) {
@ -1306,7 +1301,7 @@ public class XmppConnection implements Runnable {
this.mWaitingForSmCatchup.set(true); this.mWaitingForSmCatchup.set(true);
this.tagWriter.writeStanzaAsync(resume); this.tagWriter.writeStanzaAsync(resume);
} else if (needsBinding) { } else if (needsBinding) {
if (this.streamFeatures.hasChild("bind", Namespace.BIND) && isSecure) { if (this.streamFeatures.hasChild("bind", Namespace.BIND)) {
sendBindRequest(); sendBindRequest();
} else { } else {
Log.d( Log.d(
@ -1516,7 +1511,7 @@ public class XmppConnection implements Runnable {
} }
final Iq preAuthRequest = new Iq(Iq.Type.SET); final Iq preAuthRequest = new Iq(Iq.Type.SET);
preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth); preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
sendUnmodifiedIqPacket( sendIqPacketUnbound(
preAuthRequest, preAuthRequest,
(response) -> { (response) -> {
if (response.getType() == Iq.Type.RESULT) { if (response.getType() == Iq.Type.RESULT) {
@ -1526,15 +1521,14 @@ public class XmppConnection implements Runnable {
Log.d(Config.LOGTAG, account.address + ": failed to pre auth. " + error); Log.d(Config.LOGTAG, account.address + ": failed to pre auth. " + error);
throw new StateChangingError(ConnectionState.REGISTRATION_INVALID_TOKEN); throw new StateChangingError(ConnectionState.REGISTRATION_INVALID_TOKEN);
} }
}, });
true);
} }
private void sendRegistryRequest() { private void sendRegistryRequest() {
final Iq retrieveRegistration = new Iq(Iq.Type.GET); final Iq retrieveRegistration = new Iq(Iq.Type.GET);
retrieveRegistration.addExtension(new Register()); retrieveRegistration.addExtension(new Register());
retrieveRegistration.setTo(account.address.getDomain()); retrieveRegistration.setTo(account.address.getDomain());
sendUnmodifiedIqPacket( sendIqPacketUnbound(
retrieveRegistration, retrieveRegistration,
(packet) -> { (packet) -> {
if (packet.getType() == Iq.Type.TIMEOUT) { if (packet.getType() == Iq.Type.TIMEOUT) {
@ -1562,8 +1556,7 @@ public class XmppConnection implements Runnable {
register.addChild(username); register.addChild(username);
register.addChild(password); register.addChild(password);
registrationRequest.setFrom(account.address); registrationRequest.setFrom(account.address);
sendUnmodifiedIqPacket( sendIqPacketUnbound(registrationRequest, this::handleRegistrationResponse);
registrationRequest, this::handleRegistrationResponse, true);
} else if (query.hasChild("x", Namespace.DATA)) { } else if (query.hasChild("x", Namespace.DATA)) {
final Data data = Data.parse(query.findChild("x", Namespace.DATA)); final Data data = Data.parse(query.findChild("x", Namespace.DATA));
final Element blob = query.findChild("data", "urn:xmpp:bob"); final Element blob = query.findChild("data", "urn:xmpp:bob");
@ -1621,15 +1614,14 @@ public class XmppConnection implements Runnable {
} }
throw new StateChangingError(ConnectionState.REGISTRATION_FAILED); throw new StateChangingError(ConnectionState.REGISTRATION_FAILED);
} }
}, });
true);
} }
private void handleRegistrationResponse(final Iq packet) { private void handleRegistrationResponse(final Iq packet) {
if (packet.getType() == Iq.Type.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
ConversationsDatabase.getInstance(context) ConversationsDatabase.getInstance(context)
.accountDao() .accountDao()
.setPendingRegistration(account.id, false); .setLoginAndBind(account.id, true);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
account.address + ": successfully registered new account on server"); account.address + ": successfully registered new account on server");
@ -1693,7 +1685,7 @@ public class XmppConnection implements Runnable {
} }
final Iq iq = new Iq(Iq.Type.SET); final Iq iq = new Iq(Iq.Type.SET);
iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource); iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource);
this.sendUnmodifiedIqPacket( this.sendIqPacketUnbound(
iq, iq,
(packet) -> { (packet) -> {
if (packet.getType() == Iq.Type.TIMEOUT) { if (packet.getType() == Iq.Type.TIMEOUT) {
@ -1759,8 +1751,7 @@ public class XmppConnection implements Runnable {
+ ")"); + ")");
} }
throw new StateChangingError(ConnectionState.BIND_FAILURE); throw new StateChangingError(ConnectionState.BIND_FAILURE);
}, });
true);
} }
private void setConnectionAddress(final Jid jid) { private void setConnectionAddress(final Jid jid) {
@ -1813,7 +1804,7 @@ public class XmppConnection implements Runnable {
Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server"); Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server");
final Iq startSession = new Iq(Iq.Type.SET); final Iq startSession = new Iq(Iq.Type.SET);
startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
this.sendUnmodifiedIqPacket( this.sendIqPacketUnbound(
startSession, startSession,
(packet) -> { (packet) -> {
if (packet.getType() == Iq.Type.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
@ -1822,8 +1813,7 @@ public class XmppConnection implements Runnable {
} else if (packet.getType() != Iq.Type.TIMEOUT) { } else if (packet.getType() != Iq.Type.TIMEOUT) {
throw new StateChangingError(ConnectionState.SESSION_FAILURE); throw new StateChangingError(ConnectionState.SESSION_FAILURE);
} }
}, });
true);
} }
// TODO the return value is not used any more // TODO the return value is not used any more
@ -2009,6 +1999,14 @@ public class XmppConnection implements Runnable {
} }
public ListenableFuture<Iq> sendIqPacket(final Iq packet) { public ListenableFuture<Iq> sendIqPacket(final Iq packet) {
return sendIqPacket(packet, false);
}
public ListenableFuture<Iq> sendIqPacketUnbound(final Iq packet) {
return sendIqPacket(packet, true);
}
private ListenableFuture<Iq> sendIqPacket(final Iq packet, final boolean sendToUnboundStream) {
final SettableFuture<Iq> future = SettableFuture.create(); final SettableFuture<Iq> future = SettableFuture.create();
sendIqPacket( sendIqPacket(
packet, packet,
@ -2021,17 +2019,21 @@ public class XmppConnection implements Runnable {
} else { } else {
future.setException(new IqErrorException(result)); future.setException(new IqErrorException(result));
} }
}); },
sendToUnboundStream);
return future; return future;
} }
public String sendIqPacket(final Iq packet, final Consumer<Iq> callback) { public void sendIqPacket(final Iq packet, final Consumer<Iq> callback) {
packet.setFrom(account.address); this.sendIqPacket(packet, callback, false);
return this.sendUnmodifiedIqPacket(packet, callback, false);
} }
public synchronized String sendUnmodifiedIqPacket( public void sendIqPacketUnbound(final Iq packet, final Consumer<Iq> callback) {
final Iq packet, final Consumer<Iq> callback, boolean force) { this.sendIqPacket(packet, callback, true);
}
private synchronized void sendIqPacket(
final Iq packet, final Consumer<Iq> callback, final boolean sendToUnboundStream) {
if (Strings.isNullOrEmpty(packet.getId())) { if (Strings.isNullOrEmpty(packet.getId())) {
packet.setId(IDs.medium()); packet.setId(IDs.medium());
} }
@ -2040,8 +2042,7 @@ public class XmppConnection implements Runnable {
packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
} }
} }
this.sendPacket(packet, force); this.sendPacket(packet, sendToUnboundStream);
return packet.getId();
} }
public void sendResultFor(final Iq request, final Extension... extensions) { public void sendResultFor(final Iq request, final Extension... extensions) {
@ -2079,14 +2080,15 @@ public class XmppConnection implements Runnable {
sendPacket(packet, false); sendPacket(packet, false);
} }
private synchronized void sendPacket(final StreamElement packet, final boolean force) { private synchronized void sendPacket(
final StreamElement packet, final boolean sendToUnboundStream) {
if (stanzasSent == Integer.MAX_VALUE) { if (stanzasSent == Integer.MAX_VALUE) {
resetStreamId(); resetStreamId();
disconnect(true); disconnect(true);
return; return;
} }
synchronized (this.mStanzaQueue) { synchronized (this.mStanzaQueue) {
if (force || isBound) { if (sendToUnboundStream || isBound) {
tagWriter.writeStanzaAsync(packet); tagWriter.writeStanzaAsync(packet);
} else { } else {
Log.d( Log.d(
@ -2131,11 +2133,17 @@ public class XmppConnection implements Runnable {
} }
public void sendPing() { public void sendPing() {
if (!r()) { if (this.inSmacksSession) {
this.tagWriter.writeStanzaAsync(new Request());
} else {
final Iq iq = new Iq(Iq.Type.GET); final Iq iq = new Iq(Iq.Type.GET);
iq.setFrom(account.address); iq.setFrom(account.address);
iq.addExtension(new Ping()); iq.addExtension(new Ping());
this.sendIqPacket(iq, null); this.sendIqPacket(
iq,
response -> {
LOGGER.info("Server responded to ping");
});
} }
this.lastPingSent = SystemClock.elapsedRealtime(); this.lastPingSent = SystemClock.elapsedRealtime();
} }
@ -2213,15 +2221,6 @@ public class XmppConnection implements Runnable {
this.streamId = null; this.streamId = null;
} }
public boolean r() {
if (this.inSmacksSession) {
this.tagWriter.writeStanzaAsync(new Request());
return true;
} else {
return false;
}
}
public int getTimeToNextAttempt() { public int getTimeToNextAttempt() {
final int additionalTime = final int additionalTime =
recentErrorConnectionState == ConnectionState.POLICY_VIOLATION ? 3 : 0; recentErrorConnectionState == ConnectionState.POLICY_VIOLATION ? 3 : 0;

View file

@ -10,16 +10,23 @@ import eu.siacs.conversations.xml.Namespace;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.data.Data; import im.conversations.android.xmpp.model.data.Data;
import im.conversations.android.xmpp.model.oob.OutOfBandData; import im.conversations.android.xmpp.model.oob.OutOfBandData;
import im.conversations.android.xmpp.model.pars.PreAuth;
import im.conversations.android.xmpp.model.register.Instructions; import im.conversations.android.xmpp.model.register.Instructions;
import im.conversations.android.xmpp.model.register.Password; import im.conversations.android.xmpp.model.register.Password;
import im.conversations.android.xmpp.model.register.Register; import im.conversations.android.xmpp.model.register.Register;
import im.conversations.android.xmpp.model.register.Remove; import im.conversations.android.xmpp.model.register.Remove;
import im.conversations.android.xmpp.model.register.Username; import im.conversations.android.xmpp.model.register.Username;
import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Iq;
import java.util.Arrays;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import okhttp3.HttpUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegistrationManager extends AbstractManager { public class RegistrationManager extends AbstractManager {
private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationManager.class);
public RegistrationManager(Context context, XmppConnection connection) { public RegistrationManager(Context context, XmppConnection connection) {
super(context, connection); super(context, connection);
} }
@ -27,6 +34,7 @@ public class RegistrationManager extends AbstractManager {
public ListenableFuture<Void> setPassword(final String password) { public ListenableFuture<Void> setPassword(final String password) {
final var account = getAccount(); final var account = getAccount();
final var iq = new Iq(Iq.Type.SET); final var iq = new Iq(Iq.Type.SET);
iq.setTo(account.address.getDomain());
final var register = iq.addExtension(new Register()); final var register = iq.addExtension(new Register());
register.addUsername(account.address.getEscapedLocal()); register.addUsername(account.address.getEscapedLocal());
register.addPassword(password); register.addPassword(password);
@ -36,6 +44,7 @@ public class RegistrationManager extends AbstractManager {
public ListenableFuture<Void> unregister() { public ListenableFuture<Void> unregister() {
final var iq = new Iq(Iq.Type.SET); final var iq = new Iq(Iq.Type.SET);
iq.setTo(getAccount().address.getDomain());
final var register = iq.addExtension(new Register()); final var register = iq.addExtension(new Register());
register.addExtension(new Remove()); register.addExtension(new Remove());
return Futures.transform( return Futures.transform(
@ -43,10 +52,11 @@ public class RegistrationManager extends AbstractManager {
} }
public ListenableFuture<Registration> getRegistration() { public ListenableFuture<Registration> getRegistration() {
final var iq = new Iq(Iq.Type.SET); final var iq = new Iq(Iq.Type.GET);
iq.setTo(getAccount().address.getDomain());
iq.addExtension(new Register()); iq.addExtension(new Register());
return Futures.transform( return Futures.transform(
connection.sendIqPacket(iq), connection.sendIqPacketUnbound(iq),
result -> { result -> {
final var register = result.getExtension(Register.class); final var register = result.getExtension(Register.class);
if (register == null) { if (register == null) {
@ -58,7 +68,11 @@ public class RegistrationManager extends AbstractManager {
return new SimpleRegistration(); return new SimpleRegistration();
} }
final var data = register.getExtension(Data.class); final var data = register.getExtension(Data.class);
if (data != null && Namespace.REGISTER.equals(data.getFormType())) { // note that the captcha namespace is incorrect here. That namespace is only
// used in message challenges. ejabberd uses the incorrect namespace though
if (data != null
&& Arrays.asList(Namespace.REGISTER, Namespace.CAPTCHA)
.contains(data.getFormType())) {
return new ExtendedRegistration(data); return new ExtendedRegistration(data);
} }
final var oob = register.getExtension(OutOfBandData.class); final var oob = register.getExtension(OutOfBandData.class);
@ -67,14 +81,14 @@ public class RegistrationManager extends AbstractManager {
instructions == null ? null : instructions.getContent(); instructions == null ? null : instructions.getContent();
final String redirectUrl = oob == null ? null : oob.getURL(); final String redirectUrl = oob == null ? null : oob.getURL();
if (redirectUrl != null) { if (redirectUrl != null) {
return new RedirectRegistration(redirectUrl); return RedirectRegistration.ifValid(redirectUrl);
} }
if (instructionsText != null) { if (instructionsText != null) {
final Matcher matcher = Patterns.WEB_URL.matcher(instructionsText); final Matcher matcher = Patterns.WEB_URL.matcher(instructionsText);
if (matcher.find()) { if (matcher.find()) {
final String instructionsUrl = final String instructionsUrl =
instructionsText.substring(matcher.start(), matcher.end()); instructionsText.substring(matcher.start(), matcher.end());
return new RedirectRegistration(instructionsUrl); return RedirectRegistration.ifValid(instructionsUrl);
} }
} }
throw new IllegalStateException("No supported registration method found"); throw new IllegalStateException("No supported registration method found");
@ -82,7 +96,16 @@ public class RegistrationManager extends AbstractManager {
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} }
private abstract static class Registration {} public ListenableFuture<Void> sendPreAuthentication(final String token) {
final var iq = new Iq(Iq.Type.GET);
iq.setTo(getAccount().address.getDomain());
final var preAuthentication = iq.addExtension(new PreAuth());
preAuthentication.setToken(token);
return Futures.transform(
connection.sendIqPacketUnbound(iq), result -> null, MoreExecutors.directExecutor());
}
public abstract static class Registration {}
// only requires Username + Password // only requires Username + Password
public static class SimpleRegistration extends Registration {} public static class SimpleRegistration extends Registration {}
@ -102,14 +125,23 @@ public class RegistrationManager extends AbstractManager {
// Redirection as show here: https://xmpp.org/extensions/xep-0077.html#redirect // Redirection as show here: https://xmpp.org/extensions/xep-0077.html#redirect
public static class RedirectRegistration extends Registration { public static class RedirectRegistration extends Registration {
private final String url; private final HttpUrl url;
public RedirectRegistration(@NonNull final String url) { private RedirectRegistration(@NonNull HttpUrl url) {
this.url = url; this.url = url;
} }
public @NonNull String getURL() { public @NonNull HttpUrl getURL() {
return this.url; return this.url;
} }
public static RedirectRegistration ifValid(final String url) {
final HttpUrl httpUrl = HttpUrl.parse(url);
if (httpUrl != null && httpUrl.isHttps()) {
return new RedirectRegistration(httpUrl);
}
throw new IllegalStateException(
"A URL found the registration instructions is not valid");
}
} }
} }

View file

@ -0,0 +1,17 @@
package im.conversations.android.xmpp.model.pars;
import eu.siacs.conversations.xml.Namespace;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement(namespace = Namespace.PARS)
public class PreAuth extends Extension {
public PreAuth() {
super(PreAuth.class);
}
public void setToken(final String token) {
this.setAttribute("token", token);
}
}

View file

@ -24,14 +24,6 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
final var account = getAccount(); final var account = getAccount();
final var database = getDatabase(); final var database = getDatabase();
final boolean firstLogin =
database.accountDao().setLoggedInSuccessfully(account.id, true) > 0;
if (firstLogin) {
// TODO publish display name if this is the first attempt
// IIRC this is used when the display name is set from a certificate or something
}
database.presenceDao().deletePresences(account.id); database.presenceDao().deletePresences(account.id);
getManager(RosterManager.class).fetch(); getManager(RosterManager.class).fetch();