X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/2351a89b0046d54f0830c536cc504a04436cf2b4..474372da6b56942638b7b870eb748a0a8f269cfe:/src/main/java/org/asamk/signal/Manager.java diff --git a/src/main/java/org/asamk/signal/Manager.java b/src/main/java/org/asamk/signal/Manager.java index d06a07c0..e817c0d0 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -27,6 +27,16 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.http.util.TextUtils; import org.asamk.Signal; +import org.asamk.signal.storage.contacts.ContactInfo; +import org.asamk.signal.storage.contacts.JsonContactsStore; +import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.storage.groups.JsonGroupStore; +import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; +import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; +import org.asamk.signal.storage.threads.JsonThreadStore; +import org.asamk.signal.storage.threads.ThreadInfo; +import org.asamk.signal.util.Base64; +import org.asamk.signal.util.Util; import org.whispersystems.libsignal.*; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECKeyPair; @@ -53,6 +63,7 @@ import org.whispersystems.signalservice.api.push.exceptions.*; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; +import org.whispersystems.signalservice.internal.push.SignalServiceUrl; import java.io.*; import java.net.URI; @@ -77,6 +88,7 @@ import static java.nio.file.attribute.PosixFilePermission.*; class Manager implements Signal { private final static String URL = "https://textsecure-service.whispersystems.org"; private final static TrustStore TRUST_STORE = new WhisperTrustStore(); + private final static SignalServiceUrl[] serviceUrls = new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}; public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle(); public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion(); @@ -108,6 +120,7 @@ class Manager implements Signal { private JsonGroupStore groupStore; private JsonContactsStore contactStore; private JsonThreadStore threadStore; + private SignalServiceMessagePipe messagePipe = null; public Manager(String username, String settingsPath) { this.username = username; @@ -217,7 +230,7 @@ class Manager implements Signal { migrateLegacyConfigs(); - accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, deviceId, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceUrls, username, password, deviceId, USER_AGENT); try { if (registered && accountManager.getPreKeysCount() < PREKEY_MINIMUM_COUNT) { refreshPreKeys(); @@ -342,7 +355,7 @@ class Manager implements Signal { public void register(boolean voiceVerification) throws IOException { password = Util.getSecret(18); - accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceUrls, username, password, USER_AGENT); if (voiceVerification) accountManager.requestVoiceVerificationCode(); @@ -353,10 +366,21 @@ class Manager implements Signal { save(); } + public void updateAccountAttributes() throws IOException { + accountManager.setAccountAttributes(signalingKey, signalProtocolStore.getLocalRegistrationId(), false, false, true); + } + + 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()); + } + public URI getDeviceLinkUri() throws TimeoutException, IOException { password = Util.getSecret(18); - accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceUrls, username, password, USER_AGENT); String uuid = accountManager.getNewDeviceUuid(); registered = false; @@ -493,7 +517,7 @@ class Manager implements Signal { public void verifyAccount(String verificationCode) throws IOException { verificationCode = verificationCode.replace("-", ""); signalingKey = Util.getSecret(52); - accountManager.verifyAccountWithCode(verificationCode, signalingKey, signalProtocolStore.getLocalRegistrationId(), false, true); + accountManager.verifyAccountWithCode(verificationCode, signalingKey, signalProtocolStore.getLocalRegistrationId(), false, false, true); //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); registered = true; @@ -567,6 +591,10 @@ class Manager implements Signal { throw new NotAGroupMemberException(groupId, g.name); } + public List getGroups() { + return groupStore.getGroups(); + } + @Override public void sendGroupMessage(String messageText, List attachments, byte[] groupId) @@ -761,6 +789,65 @@ class Manager implements Signal { sendMessage(messageBuilder, recipients); } + @Override + public String getContactName(String number) { + ContactInfo contact = contactStore.getContact(number); + if (contact == null) { + return ""; + } else { + return contact.name; + } + } + + @Override + public void setContactName(String number, String name) { + ContactInfo contact = contactStore.getContact(number); + if (contact == null) { + contact = new ContactInfo(); + contact.number = number; + System.out.println("Add contact " + number + " named " + name); + } else { + System.out.println("Updating contact " + number + " name " + contact.name + " -> " + name); + } + contact.name = name; + contactStore.updateContact(contact); + save(); + } + + @Override + public String getGroupName(byte[] groupId) { + GroupInfo group = getGroup(groupId); + if (group == null) { + return ""; + } else { + return group.name; + } + } + + @Override + public List getGroupMembers(byte[] groupId) { + GroupInfo group = getGroup(groupId); + if (group == null) { + return new ArrayList(); + } else { + return new ArrayList(group.members); + } + } + + @Override + public void updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException { + if (name.isEmpty()) { + name = null; + } + if (members.size() == 0) { + members = null; + } + if (avatar.isEmpty()) { + avatar = null; + } + sendUpdateGroupMessage(groupId, name, members, avatar); + } + private void requestSyncGroups() throws IOException { SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build(); SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); @@ -783,8 +870,8 @@ class Manager implements Signal { private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { - SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, - deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceUrls, username, password, + deviceId, signalProtocolStore, USER_AGENT, Optional.fromNullable(messagePipe), Optional.absent()); try { messageSender.sendMessage(message); } catch (UntrustedIdentityException e) { @@ -800,8 +887,8 @@ class Manager implements Signal { SignalServiceDataMessage message = null; try { - SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, - deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceUrls, username, password, + deviceId, signalProtocolStore, USER_AGENT, Optional.fromNullable(messagePipe), Optional.absent()); message = messageBuilder.build(); if (message.getGroupInfo().isPresent()) { @@ -883,7 +970,7 @@ class Manager implements Signal { void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e); } - private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, String source, String destination) { + private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, String source, String destination, boolean ignoreAttachments) { String threadId; if (message.getGroupInfo().isPresent()) { SignalServiceGroup groupInfo = message.getGroupInfo().get(); @@ -970,7 +1057,7 @@ class Manager implements Signal { threadStore.updateThread(thread); } } - if (message.getAttachments().isPresent()) { + if (message.getAttachments().isPresent() && !ignoreAttachments) { for (SignalServiceAttachment attachment : message.getAttachments().get()) { if (attachment.isPointer()) { try { @@ -983,7 +1070,7 @@ class Manager implements Signal { } } - public void retryFailedReceivedMessages(ReceiveMessageHandler handler) { + public void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) { final File cachePath = new File(getMessageCachePath()); if (!cachePath.exists()) { return; @@ -1014,7 +1101,7 @@ class Manager implements Signal { } catch (Exception e) { continue; } - handleMessage(envelope, content); + handleMessage(envelope, content, ignoreAttachments); } save(); handler.handleMessage(envelope, content, null); @@ -1027,13 +1114,14 @@ class Manager implements Signal { } } - public void receiveMessages(int timeoutSeconds, boolean returnOnTimeout, ReceiveMessageHandler handler) throws IOException { - retryFailedReceivedMessages(handler); - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); - SignalServiceMessagePipe messagePipe = null; + public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { + retryFailedReceivedMessages(handler, ignoreAttachments); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); try { - messagePipe = messageReceiver.createMessagePipe(); + if (messagePipe == null) { + messagePipe = messageReceiver.createMessagePipe(); + } while (true) { SignalServiceEnvelope envelope; @@ -1041,7 +1129,7 @@ class Manager implements Signal { Exception exception = null; final long now = new Date().getTime(); try { - envelope = messagePipe.read(timeoutSeconds, TimeUnit.SECONDS, new SignalServiceMessagePipe.MessagePipeCallback() { + envelope = messagePipe.read(timeout, unit, new SignalServiceMessagePipe.MessagePipeCallback() { @Override public void onMessage(SignalServiceEnvelope envelope) { // store message on disk, before acknowledging receipt to the server @@ -1067,7 +1155,7 @@ class Manager implements Signal { } catch (Exception e) { exception = e; } - handleMessage(envelope, content); + handleMessage(envelope, content, ignoreAttachments); } save(); handler.handleMessage(envelope, content, exception); @@ -1082,22 +1170,24 @@ class Manager implements Signal { } } } finally { - if (messagePipe != null) + if (messagePipe != null) { messagePipe.shutdown(); + messagePipe = null; + } } } - private void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) { + private void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments) { if (content != null) { if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); - handleSignalServiceDataMessage(message, false, envelope.getSource(), username); + handleSignalServiceDataMessage(message, false, envelope.getSource(), username, ignoreAttachments); } if (content.getSyncMessage().isPresent()) { SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); if (syncMessage.getSent().isPresent()) { SignalServiceDataMessage message = syncMessage.getSent().get().getMessage(); - handleSignalServiceDataMessage(message, true, envelope.getSource(), syncMessage.getSent().get().getDestination().get()); + handleSignalServiceDataMessage(message, true, envelope.getSource(), syncMessage.getSent().get().getDestination().get(), ignoreAttachments); } if (syncMessage.getRequest().isPresent()) { RequestMessage rm = syncMessage.getRequest().get(); @@ -1223,26 +1313,26 @@ class Manager implements Signal { private void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException { try (FileOutputStream f = new FileOutputStream(file)) { - DataOutputStream out = new DataOutputStream(f); - out.writeInt(1); // version - out.writeInt(envelope.getType()); - out.writeUTF(envelope.getSource()); - out.writeInt(envelope.getSourceDevice()); - out.writeUTF(envelope.getRelay()); - out.writeLong(envelope.getTimestamp()); - if (envelope.hasContent()) { - out.writeInt(envelope.getContent().length); - out.write(envelope.getContent()); - } else { - out.writeInt(0); - } - if (envelope.hasLegacyMessage()) { - out.writeInt(envelope.getLegacyMessage().length); - out.write(envelope.getLegacyMessage()); - } else { - out.writeInt(0); + try (DataOutputStream out = new DataOutputStream(f)) { + out.writeInt(1); // version + out.writeInt(envelope.getType()); + out.writeUTF(envelope.getSource()); + out.writeInt(envelope.getSourceDevice()); + out.writeUTF(envelope.getRelay()); + out.writeLong(envelope.getTimestamp()); + if (envelope.hasContent()) { + out.writeInt(envelope.getContent().length); + out.write(envelope.getContent()); + } else { + out.writeInt(0); + } + if (envelope.hasLegacyMessage()) { + out.writeInt(envelope.getLegacyMessage().length); + out.write(envelope.getLegacyMessage()); + } else { + out.writeInt(0); + } } - out.close(); } } @@ -1288,9 +1378,7 @@ class Manager implements Signal { private File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException, InvalidMessageException { InputStream input = stream.getInputStream(); - OutputStream output = null; - try { - output = new FileOutputStream(outputFile); + try (OutputStream output = new FileOutputStream(outputFile)) { byte[] buffer = new byte[4096]; int read; @@ -1300,10 +1388,6 @@ class Manager implements Signal { } catch (FileNotFoundException e) { e.printStackTrace(); return null; - } finally { - if (output != null) { - output.close(); - } } return outputFile; } @@ -1311,43 +1395,31 @@ class Manager implements Signal { private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException { if (storePreview && pointer.getPreview().isPresent()) { File previewFile = new File(outputFile + ".preview"); - OutputStream output = null; - try { - output = new FileOutputStream(previewFile); + try (OutputStream output = new FileOutputStream(previewFile)) { byte[] preview = pointer.getPreview().get(); output.write(preview, 0, preview.length); } catch (FileNotFoundException e) { e.printStackTrace(); return null; - } finally { - if (output != null) { - output.close(); - } } } - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); File tmpFile = Util.createTempFile(); - InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile); - - OutputStream output = null; - try { - output = new FileOutputStream(outputFile); - byte[] buffer = new byte[4096]; - int read; + try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile)) { + try (OutputStream output = new FileOutputStream(outputFile)) { + byte[] buffer = new byte[4096]; + int read; - while ((read = input.read(buffer)) != -1) { - output.write(buffer, 0, read); + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; } - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; } finally { - if (output != null) { - output.close(); - } - input.close(); try { Files.delete(tmpFile.toPath()); } catch (IOException e) { @@ -1358,7 +1430,7 @@ class Manager implements Signal { } private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException { - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); return messageReceiver.retrieveAttachment(pointer, tmpFile); } @@ -1381,15 +1453,13 @@ class Manager implements Signal { File groupsFile = Util.createTempFile(); try { - DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(new FileOutputStream(groupsFile)); - try { + try (OutputStream fos = new FileOutputStream(groupsFile)) { + DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos); for (GroupInfo record : groupStore.getGroups()) { out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name), new ArrayList<>(record.members), createGroupAvatarAttachment(record.groupId), record.active)); } - } finally { - out.close(); } if (groupsFile.exists() && groupsFile.length() > 0) { @@ -1416,14 +1486,12 @@ class Manager implements Signal { File contactsFile = Util.createTempFile(); try { - DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactsFile)); - try { + try (OutputStream fos = new FileOutputStream(contactsFile)) { + DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos); for (ContactInfo record : contactStore.getContacts()) { out.write(new DeviceContact(record.number, Optional.fromNullable(record.name), createContactAvatarAttachment(record.number), Optional.fromNullable(record.color))); } - } finally { - out.close(); } if (contactsFile.exists() && contactsFile.length() > 0) { @@ -1474,11 +1542,11 @@ class Manager implements Signal { return false; } for (JsonIdentityKeyStore.Identity id : ids) { - if (!Arrays.equals(id.identityKey.serialize(), fingerprint)) { + if (!Arrays.equals(id.getIdentityKey().serialize(), fingerprint)) { continue; } - signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_VERIFIED); + signalProtocolStore.saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); save(); return true; } @@ -1497,11 +1565,11 @@ class Manager implements Signal { return false; } for (JsonIdentityKeyStore.Identity id : ids) { - if (!safetyNumber.equals(computeSafetyNumber(name, id.identityKey))) { + if (!safetyNumber.equals(computeSafetyNumber(name, id.getIdentityKey()))) { continue; } - signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_VERIFIED); + signalProtocolStore.saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); save(); return true; } @@ -1519,8 +1587,8 @@ class Manager implements Signal { return false; } for (JsonIdentityKeyStore.Identity id : ids) { - if (id.trustLevel == TrustLevel.UNTRUSTED) { - signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_UNVERIFIED); + if (id.getTrustLevel() == TrustLevel.UNTRUSTED) { + signalProtocolStore.saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); } } save();