X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/f3878c54a627d278e503de1f4497a86f76519bc8..e508fc50e9d9efcc9259897c27ed9ba13d19de42:/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 44ec6445..c2b139fe 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -17,7 +17,11 @@ package org.asamk.signal.manager; import org.asamk.Signal; -import org.asamk.signal.*; +import org.asamk.signal.AttachmentInvalidException; +import org.asamk.signal.GroupNotFoundException; +import org.asamk.signal.NotAGroupMemberException; +import org.asamk.signal.TrustLevel; +import org.asamk.signal.UserAlreadyExists; import org.asamk.signal.storage.SignalAccount; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.groups.GroupInfo; @@ -26,8 +30,22 @@ import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.threads.ThreadInfo; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; -import org.signal.libsignal.metadata.*; -import org.whispersystems.libsignal.*; +import org.signal.libsignal.metadata.InvalidMetadataMessageException; +import org.signal.libsignal.metadata.InvalidMetadataVersionException; +import org.signal.libsignal.metadata.ProtocolDuplicateMessageException; +import org.signal.libsignal.metadata.ProtocolInvalidKeyException; +import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException; +import org.signal.libsignal.metadata.ProtocolInvalidMessageException; +import org.signal.libsignal.metadata.ProtocolInvalidVersionException; +import org.signal.libsignal.metadata.ProtocolLegacyMessageException; +import org.signal.libsignal.metadata.ProtocolNoSessionException; +import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; +import org.signal.libsignal.metadata.SelfSendException; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.IdentityKeyPair; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.InvalidMessageException; +import org.whispersystems.libsignal.InvalidVersionException; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECKeyPair; import org.whispersystems.libsignal.ecc.ECPublicKey; @@ -44,8 +62,26 @@ 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.messages.*; -import org.whispersystems.signalservice.api.messages.multidevice.*; +import org.whispersystems.signalservice.api.messages.SendMessageResult; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; +import org.whispersystems.signalservice.api.messages.SignalServiceContent; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; +import org.whispersystems.signalservice.api.messages.SignalServiceGroup; +import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; +import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; @@ -54,16 +90,35 @@ import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureExcept import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.SleepTimer; +import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; +import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; import org.whispersystems.signalservice.internal.util.Base64; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -136,6 +191,7 @@ public class Manager implements Signal { } } catch (AuthorizationFailedException e) { System.err.println("Authorization failed, was the number registered elsewhere?"); + throw e; } } @@ -189,9 +245,9 @@ public class Manager implements Signal { accountManager = new SignalServiceAccountManager(BaseConfig.serviceConfiguration, account.getUsername(), account.getPassword(), BaseConfig.USER_AGENT, timer); if (voiceVerification) { - accountManager.requestVoiceVerificationCode(Locale.getDefault()); + accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent()); } else { - accountManager.requestSmsVerificationCode(false); + accountManager.requestSmsVerificationCode(false, Optional.absent(), Optional.absent()); } account.setRegistered(false); @@ -202,11 +258,28 @@ public class Manager implements Signal { accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), getSelfUnidentifiedAccessKey(), false); } + public void setProfileName(String name) throws IOException { + accountManager.setProfileName(account.getProfileKey(), name); + } + + public void setProfileAvatar(File avatar) throws IOException { + final StreamDetails streamDetails = Utils.createStreamDetailsFromFile(avatar); + accountManager.setProfileAvatar(account.getProfileKey(), streamDetails); + streamDetails.getStream().close(); + } + + public void removeProfileAvatar() throws IOException { + accountManager.setProfileAvatar(account.getProfileKey(), null); + } + public void unregister() throws IOException { // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false. // If this is the master device, other users can't send messages to this number anymore. // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore. accountManager.setGcmId(Optional.absent()); + + account.setRegistered(false); + account.save(); } public String getDeviceLinkUri() throws TimeoutException, IOException { @@ -341,6 +414,10 @@ public class Manager implements Signal { accountManager.setPreKeys(getIdentity(), signedPreKeyRecord, oneTimePreKeys); } + private SignalServiceMessageReceiver getMessageReceiver() { + return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, username, account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer); + } + private Optional createGroupAvatarAttachment(byte[] groupId) throws IOException { File file = getGroupAvatarFile(groupId); if (!file.exists()) { @@ -510,8 +587,15 @@ public class Manager implements Signal { } } - return SignalServiceDataMessage.newBuilder() + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()); + + ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(g.groupId)); + if (thread != null) { + messageBuilder.withExpiration(thread.messageExpirationTime); + } + + return messageBuilder; } private void sendGroupInfoRequest(byte[] groupId, String recipient) throws IOException, EncapsulatedExceptions { @@ -525,6 +609,11 @@ public class Manager implements Signal { SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()); + ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId)); + if (thread != null) { + messageBuilder.withExpiration(thread.messageExpirationTime); + } + // Send group info request message to the recipient who sent us a message with this groupId final List membersSend = new ArrayList<>(); membersSend.add(recipient); @@ -547,6 +636,7 @@ public class Manager implements Signal { if (attachments != null) { messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments)); } + messageBuilder.withProfileKey(account.getProfileKey()); sendMessageLegacy(messageBuilder, recipients); } @@ -762,7 +852,8 @@ public class Manager implements Signal { message = messageBuilder.build(); if (message.getGroupInfo().isPresent()) { try { - List result = messageSender.sendMessage(new ArrayList<>(recipientsTS), getAccessFor(recipientsTS), message); + final boolean isRecipientUpdate = false; + List result = messageSender.sendMessage(new ArrayList<>(recipientsTS), getAccessFor(recipientsTS), isRecipientUpdate, message); for (SendMessageResult r : result) { if (r.getIdentityFailure() != null) { account.getSignalProtocolStore().saveIdentity(r.getAddress().getNumber(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED); @@ -773,6 +864,25 @@ public class Manager implements Signal { account.getSignalProtocolStore().saveIdentity(e.getE164Number(), e.getIdentityKey(), TrustLevel.UNTRUSTED); return Collections.emptyList(); } + } else if (recipientsTS.size() == 1 && recipientsTS.contains(new SignalServiceAddress(username))) { + SignalServiceAddress recipient = new SignalServiceAddress(username); + final Optional unidentifiedAccess = getAccessFor(recipient); + SentTranscriptMessage transcript = new SentTranscriptMessage(recipient.getNumber(), + message.getTimestamp(), + message, + message.getExpiresInSeconds(), + Collections.singletonMap(recipient.getNumber(), unidentifiedAccess.isPresent()), + false); + SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); + + List results = new ArrayList<>(recipientsTS.size()); + try { + messageSender.sendMessage(syncMessage, unidentifiedAccess); + } catch (UntrustedIdentityException e) { + account.getSignalProtocolStore().saveIdentity(e.getE164Number(), e.getIdentityKey(), TrustLevel.UNTRUSTED); + results.add(SendMessageResult.identityFailure(recipient, e.getIdentityKey())); + } + return results; } else { // Send to all individually, so sync messages are sent correctly List results = new ArrayList<>(recipientsTS.size()); @@ -804,7 +914,7 @@ public class Manager implements Signal { } } - private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException { + private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException, UnsupportedDataMessageException { SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), account.getSignalProtocolStore(), Utils.getCertificateValidator()); try { return cipher.decrypt(envelope); @@ -978,7 +1088,7 @@ public class Manager implements Signal { public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { retryFailedReceivedMessages(handler, ignoreAttachments); - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, username, account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer); + final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); try { if (messagePipe == null) { @@ -1149,6 +1259,10 @@ public class Manager implements Signal { } if (c.getExpirationTimer().isPresent()) { ThreadInfo thread = account.getThreadStore().getThread(c.getNumber()); + if (thread == null) { + thread = new ThreadInfo(); + thread.id = c.getNumber(); + } thread.messageExpirationTime = c.getExpirationTimer().get(); account.getThreadStore().updateThread(thread); } @@ -1236,7 +1350,7 @@ public class Manager implements Signal { } } - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, username, account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer); + final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); File tmpFile = IOUtils.createTempFile(); try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE)) { @@ -1262,7 +1376,7 @@ public class Manager implements Signal { } private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException { - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, username, account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer); + final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE); } @@ -1306,7 +1420,7 @@ public class Manager implements Signal { } } - private void sendContacts() throws IOException, UntrustedIdentityException { + public void sendContacts() throws IOException, UntrustedIdentityException { File contactsFile = IOUtils.createTempFile(); try { @@ -1370,6 +1484,10 @@ public class Manager implements Signal { sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); } + public List getContacts() { + return account.getContactStore().getContacts(); + } + public ContactInfo getContact(String number) { return account.getContactStore().getContact(number); }