From: AsamK Date: Sun, 9 May 2021 10:22:44 +0000 (+0200) Subject: Allow relinking an account if it's no longer authorized X-Git-Tag: v0.8.2~11 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/ab95e635cebb33396240cbaaa6eba882350d6877?ds=sidebyside Allow relinking an account if it's no longer authorized --- diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index acd48c34..8632129c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -288,7 +288,7 @@ public class Manager implements Closeable { throw new NotRegisteredException(); } - var account = SignalAccount.load(pathConfig.getDataPath(), username); + var account = SignalAccount.load(pathConfig.getDataPath(), username, true); if (!account.isRegistered()) { throw new NotRegisteredException(); diff --git a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index e6fe2a60..b0bfebf8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -29,6 +29,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; @@ -97,7 +98,7 @@ public class ProvisioningManager { logger.info("Received link information from {}, linking in progress ...", number); - if (SignalAccount.userExists(pathConfig.getDataPath(), number)) { + if (SignalAccount.userExists(pathConfig.getDataPath(), number) && !canRelinkExistingAccount(number)) { throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.getDataPath(), number)); } @@ -116,7 +117,7 @@ public class ProvisioningManager { SignalAccount account = null; try { - account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), + account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.getDataPath(), number, ret.getUuid(), password, @@ -133,7 +134,7 @@ public class ProvisioningManager { try { m.refreshPreKeys(); } catch (Exception e) { - logger.error("Failed to refresh prekeys."); + logger.error("Failed to check new account state."); throw e; } @@ -160,4 +161,31 @@ public class ProvisioningManager { } } } + + private boolean canRelinkExistingAccount(final String number) throws UserAlreadyExists, IOException { + final SignalAccount signalAccount; + try { + signalAccount = SignalAccount.load(pathConfig.getDataPath(), number, false); + } catch (IOException e) { + logger.debug("Account in use or failed to load.", e); + return false; + } + + try (signalAccount) { + if (signalAccount.isMasterDevice()) { + logger.debug("Account is a master device."); + return false; + } + + final var m = new Manager(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent); + try (m) { + m.checkAccountState(); + } catch (AuthorizationFailedException ignored) { + return true; + } + + logger.debug("Account is still successfully linked."); + return false; + } + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 612984ed..9dae3f41 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -107,7 +107,7 @@ public class RegistrationManager implements Closeable { return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent); } - var account = SignalAccount.load(pathConfig.getDataPath(), username); + var account = SignalAccount.load(pathConfig.getDataPath(), username, true); return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index 93178a91..1620dbf2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -105,14 +105,19 @@ public class SignalAccount implements Closeable { this.lock = lock; } - public static SignalAccount load(File dataPath, String username) throws IOException { + public static SignalAccount load(File dataPath, String username, boolean waitForLock) throws IOException { final var fileName = getFileName(dataPath, username); - final var pair = openFileChannel(fileName); + final var pair = openFileChannel(fileName, waitForLock); try { var account = new SignalAccount(pair.first(), pair.second()); account.load(dataPath); account.migrateLegacyConfigs(); + if (!username.equals(account.getUsername())) { + throw new IOException("Username in account file doesn't match expected number: " + + account.getUsername()); + } + return account; } catch (Throwable e) { pair.second().close(); @@ -130,7 +135,7 @@ public class SignalAccount implements Closeable { IOUtils.createPrivateFile(fileName); } - final var pair = openFileChannel(fileName); + final var pair = openFileChannel(fileName, true); var account = new SignalAccount(pair.first(), pair.second()); account.username = username; @@ -167,7 +172,7 @@ public class SignalAccount implements Closeable { messageCache = new MessageCache(getMessageCachePath(dataPath, username)); } - public static SignalAccount createLinkedAccount( + public static SignalAccount createOrUpdateLinkedAccount( File dataPath, String username, UUID uuid, @@ -181,18 +186,51 @@ public class SignalAccount implements Closeable { IOUtils.createPrivateDirectories(dataPath); var fileName = getFileName(dataPath, username); if (!fileName.exists()) { - IOUtils.createPrivateFile(fileName); - } + return createLinkedAccount(dataPath, + username, + uuid, + password, + encryptedDeviceName, + deviceId, + identityKey, + registrationId, + profileKey); + } + + final var account = load(dataPath, username, true); + account.setProvisioningData(username, uuid, password, encryptedDeviceName, deviceId, profileKey); + account.recipientStore.resolveRecipientTrusted(account.getSelfAddress()); + account.sessionStore.archiveAllSessions(); + account.clearAllPreKeys(); + return account; + } - final var pair = openFileChannel(fileName); + private void clearAllPreKeys() { + this.preKeyIdOffset = 0; + this.nextSignedPreKeyId = 0; + this.preKeyStore.removeAllPreKeys(); + this.signedPreKeyStore.removeAllSignedPreKeys(); + save(); + } + + private static SignalAccount createLinkedAccount( + File dataPath, + String username, + UUID uuid, + String password, + String encryptedDeviceName, + int deviceId, + IdentityKeyPair identityKey, + int registrationId, + ProfileKey profileKey + ) throws IOException { + var fileName = getFileName(dataPath, username); + IOUtils.createPrivateFile(fileName); + + final var pair = openFileChannel(fileName, true); var account = new SignalAccount(pair.first(), pair.second()); - account.username = username; - account.uuid = uuid; - account.password = password; - account.profileKey = profileKey; - account.encryptedDeviceName = encryptedDeviceName; - account.deviceId = deviceId; + account.setProvisioningData(username, uuid, password, encryptedDeviceName, deviceId, profileKey); account.initStores(dataPath, identityKey, registrationId); account.groupStore = new GroupStore(getGroupCachePath(dataPath, username), @@ -200,9 +238,6 @@ public class SignalAccount implements Closeable { account::saveGroupStore); account.stickerStore = new StickerStore(account::saveStickerStore); - account.registered = true; - account.isMultiDevice = true; - account.recipientStore.resolveRecipientTrusted(account.getSelfAddress()); account.migrateLegacyConfigs(); account.save(); @@ -210,6 +245,24 @@ public class SignalAccount implements Closeable { return account; } + private void setProvisioningData( + final String username, + final UUID uuid, + final String password, + final String encryptedDeviceName, + final int deviceId, + final ProfileKey profileKey + ) { + this.username = username; + this.uuid = uuid; + this.password = password; + this.profileKey = profileKey; + this.encryptedDeviceName = encryptedDeviceName; + this.deviceId = deviceId; + this.registered = true; + this.isMultiDevice = true; + } + private void migrateLegacyConfigs() { if (getPassword() == null) { setPassword(KeyUtils.createPassword()); @@ -618,10 +671,14 @@ public class SignalAccount implements Closeable { } } - private static Pair openFileChannel(File fileName) throws IOException { + private static Pair openFileChannel(File fileName, boolean waitForLock) throws IOException { var fileChannel = new RandomAccessFile(fileName, "rw").getChannel(); var lock = fileChannel.tryLock(); if (lock == null) { + if (!waitForLock) { + logger.debug("Config file is in use by another instance."); + throw new IOException("Config file is in use by another instance."); + } logger.info("Config file is in use by another instance, waiting…"); lock = fileChannel.lock(); logger.info("Config file lock acquired."); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java index c8153c56..ccdea422 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java @@ -78,6 +78,21 @@ public class PreKeyStore implements org.whispersystems.libsignal.state.PreKeySto } } + public void removeAllPreKeys() { + final var files = preKeysPath.listFiles(); + if (files == null) { + return; + } + + for (var file : files) { + try { + Files.delete(file.toPath()); + } catch (IOException e) { + logger.error("Failed to delete pre key file {}: {}", file, e.getMessage()); + } + } + } + private File getPreKeyFile(int preKeyId) { try { IOUtils.createPrivateDirectories(preKeysPath); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java index 83176557..bc85e3e8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java @@ -91,6 +91,21 @@ public class SignedPreKeyStore implements org.whispersystems.libsignal.state.Sig } } + public void removeAllSignedPreKeys() { + final var files = signedPreKeysPath.listFiles(); + if (files == null) { + return; + } + + for (var file : files) { + try { + Files.delete(file.toPath()); + } catch (IOException e) { + logger.error("Failed to delete signed pre key file {}: {}", file, e.getMessage()); + } + } + } + private File getSignedPreKeyFile(int signedPreKeyId) { try { IOUtils.createPrivateDirectories(signedPreKeysPath); diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java index a6ef8484..f3f77347 100644 --- a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -35,7 +35,7 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { public void attachToSubparser(final Subparser subparser) { subparser.addArgument("-t", "--timeout") .type(double.class) - .setDefault(1.0) + .setDefault(3.0) .help("Number of seconds to wait for new messages (negative values disable timeout)"); subparser.addArgument("--ignore-attachments") .help("Don’t download attachments of received messages.")