]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Manager.java
Fix behavior for recipients with only UUIDs
[signal-cli] / src / main / java / org / asamk / signal / manager / Manager.java
index 7d13eddbc8c7056c5ccc39b79ec28f090a150d92..c332a9599602d9cb5c0ce95f543ce29d2b9983f3 100644 (file)
@@ -22,6 +22,8 @@ import org.asamk.signal.storage.SignalAccount;
 import org.asamk.signal.storage.contacts.ContactInfo;
 import org.asamk.signal.storage.groups.GroupInfo;
 import org.asamk.signal.storage.groups.JsonGroupStore;
+import org.asamk.signal.storage.profiles.SignalProfile;
+import org.asamk.signal.storage.profiles.SignalProfileEntry;
 import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.Util;
@@ -64,6 +66,8 @@ import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
+import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
+import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
@@ -105,6 +109,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
 import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
 import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
+import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
 import org.whispersystems.signalservice.internal.util.Hex;
 import org.whispersystems.util.Base64;
 
@@ -142,6 +147,8 @@ import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+import static org.asamk.signal.manager.ServiceConfig.capabilities;
+
 public class Manager implements Closeable {
 
     private final SleepTimer timer = new UptimeSleepTimer();
@@ -153,6 +160,7 @@ public class Manager implements Closeable {
     private SignalServiceAccountManager accountManager;
     private SignalServiceMessagePipe messagePipe = null;
     private SignalServiceMessagePipe unidentifiedMessagePipe = null;
+    private boolean discoverableByPhoneNumber = true;
 
     public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) {
         this.account = account;
@@ -173,7 +181,17 @@ public class Manager implements Closeable {
     }
 
     private SignalServiceAccountManager createSignalServiceAccountManager() {
-        return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer);
+        GroupsV2Operations groupsV2Operations;
+        try {
+            groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceConfiguration));
+        } catch (Throwable ignored) {
+            groupsV2Operations = null;
+        }
+        return new SignalServiceAccountManager(serviceConfiguration,
+                new DynamicCredentialsProvider(account.getUuid(), account.getUsername(), account.getPassword(), null, account.getDeviceId()),
+                userAgent,
+                groupsV2Operations,
+                timer);
     }
 
     private IdentityKeyPair getIdentityKeyPair() {
@@ -286,7 +304,7 @@ public class Manager implements Closeable {
     }
 
     public void updateAccountAttributes() throws IOException {
-        accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities);
+        accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities, discoverableByPhoneNumber);
     }
 
     public void setProfile(String name, File avatar) throws IOException {
@@ -371,7 +389,7 @@ public class Manager implements Closeable {
         verificationCode = verificationCode.replace("-", "");
         account.setSignalingKey(KeyUtils.createSignalingKey());
         // TODO make unrestricted unidentified access configurable
-        VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities);
+        VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities, discoverableByPhoneNumber);
 
         UUID uuid = UuidUtil.parseOrNull(response.getUuid());
         // TODO response.isStorageCapable()
@@ -439,7 +457,39 @@ public class Manager implements Closeable {
     }
 
     private SignalProfile getRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess, ProfileKey profileKey) throws IOException {
-        return decryptProfile(getEncryptedRecipientProfile(address, unidentifiedAccess), profileKey);
+        SignalProfileEntry profileEntry = account.getProfileStore().getProfile(address);
+        long now = new Date().getTime();
+        // Profiles are cache for 24h before retrieving them again
+        if (profileEntry == null || profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
+            SignalProfile profile = retrieveRecipientProfile(address, unidentifiedAccess, profileKey);
+            account.getProfileStore().updateProfile(address, profileKey, now, profile);
+            return profile;
+        }
+        return profileEntry.getProfile();
+    }
+
+    private SignalProfile retrieveRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess, ProfileKey profileKey) throws IOException {
+        final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address, unidentifiedAccess);
+
+        File avatarFile = null;
+        try {
+            avatarFile = encryptedProfile.getAvatar() == null ? null : retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
+        } catch (Throwable e) {
+            System.err.println("Failed to retrieve profile avatar, ignoring: " + e.getMessage());
+        }
+
+        ProfileCipher profileCipher = new ProfileCipher(profileKey);
+        try {
+            return new SignalProfile(
+                    encryptedProfile.getIdentityKey(),
+                    encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))),
+                    avatarFile,
+                    encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(),
+                    encryptedProfile.isUnrestrictedUnidentifiedAccess(),
+                    encryptedProfile.getCapabilities());
+        } catch (InvalidCiphertextException e) {
+            return null;
+        }
     }
 
     private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(byte[] groupId) throws IOException {
@@ -556,9 +606,7 @@ public class Manager implements Closeable {
                 for (ContactTokenDetails contact : contacts) {
                     newE164Members.remove(contact.getNumber());
                 }
-                System.err.println("Failed to add members " + Util.join(", ", newE164Members) + " to group: Not registered on Signal");
-                System.err.println("Aborting…");
-                System.exit(1);
+                throw new IOException("Failed to add members " + Util.join(", ", newE164Members) + " to group: Not registered on Signal");
             }
 
             g.addMembers(members);
@@ -838,7 +886,8 @@ public class Manager implements Closeable {
                 throw new StickerPackInvalidException("Could not find find " + sticker.file);
             }
 
-            StickerInfo stickerInfo = new StickerInfo(data.first(), data.second(), Optional.fromNullable(sticker.emoji).or(""));
+            String contentType = Utils.getFileMimeType(new File(sticker.file), null);
+            StickerInfo stickerInfo = new StickerInfo(data.first(), data.second(), Optional.fromNullable(sticker.emoji).or(""), contentType);
             stickers.add(stickerInfo);
         }
 
@@ -855,7 +904,8 @@ public class Manager implements Closeable {
                 throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
             }
 
-            cover = new StickerInfo(data.first(), data.second(), Optional.fromNullable(pack.cover.emoji).or(""));
+            String contentType = Utils.getFileMimeType(new File(pack.cover.file), null);
+            cover = new StickerInfo(data.first(), data.second(), Optional.fromNullable(pack.cover.emoji).or(""), contentType);
         }
 
         return new SignalServiceStickerManifestUpload(
@@ -927,10 +977,10 @@ public class Manager implements Closeable {
 
     private byte[] getSenderCertificate() {
         // TODO support UUID capable sender certificates
-        // byte[] certificate = accountManager.getSenderCertificate();
+        // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
         byte[] certificate;
         try {
-            certificate = accountManager.getSenderCertificateLegacy();
+            certificate = accountManager.getSenderCertificate();
         } catch (IOException e) {
             System.err.println("Failed to get sender certificate: " + e);
             return null;
@@ -943,21 +993,6 @@ public class Manager implements Closeable {
         return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey());
     }
 
-    private static SignalProfile decryptProfile(SignalServiceProfile encryptedProfile, ProfileKey profileKey) throws IOException {
-        ProfileCipher profileCipher = new ProfileCipher(profileKey);
-        try {
-            return new SignalProfile(
-                    encryptedProfile.getIdentityKey(),
-                    encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))),
-                    encryptedProfile.getAvatar(),
-                    encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(),
-                    encryptedProfile.isUnrestrictedUnidentifiedAccess()
-            );
-        } catch (InvalidCiphertextException e) {
-            return null;
-        }
-    }
-
     private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
         ContactInfo contact = account.getContactStore().getContact(recipient);
         if (contact == null || contact.profileKey == null) {
@@ -1475,7 +1510,8 @@ public class Manager implements Closeable {
             if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) {
                 File cacheFile = null;
                 try {
-                    cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp());
+                    String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "";
+                    cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp());
                     Files.delete(cacheFile.toPath());
                     // Try to delete directory if empty
                     new File(getMessageCachePath()).delete();
@@ -1717,6 +1753,29 @@ public class Manager implements Closeable {
         }
     }
 
+    private File getProfileAvatarFile(SignalServiceAddress address) {
+        return new File(pathConfig.getAvatarsPath(), "profile-" + address.getLegacyIdentifier());
+    }
+
+    private File retrieveProfileAvatar(SignalServiceAddress address, String avatarPath, ProfileKey profileKey) throws IOException {
+        IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
+        SignalServiceMessageReceiver receiver = getMessageReceiver();
+        File outputFile = getProfileAvatarFile(address);
+
+        File tmpFile = IOUtils.createTempFile();
+        try (InputStream input = receiver.retrieveProfileAvatar(avatarPath, tmpFile, profileKey, ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
+            // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
+            IOUtils.copyStreamToFile(input, outputFile, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
+        } finally {
+            try {
+                Files.delete(tmpFile.toPath());
+            } catch (IOException e) {
+                System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage());
+            }
+        }
+        return outputFile;
+    }
+
     public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
         return new File(pathConfig.getAttachmentsPath(), attachmentId.toString());
     }
@@ -1742,17 +1801,7 @@ public class Manager implements Closeable {
 
         File tmpFile = IOUtils.createTempFile();
         try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE)) {
-            try (OutputStream output = new FileOutputStream(outputFile)) {
-                byte[] buffer = new byte[4096];
-                int read;
-
-                while ((read = input.read(buffer)) != -1) {
-                    output.write(buffer, 0, read);
-                }
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
-                return null;
-            }
+            IOUtils.copyStreamToFile(input, outputFile);
         } finally {
             try {
                 Files.delete(tmpFile.toPath());