X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/93e2c58fcfd1777c80193bbd7ea017fd78371b6c..ae3e5be1241b3ae9d27fff950c3bb54bdcdc32e7:/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 6c73d2c4..77647d27 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -107,6 +107,7 @@ class Manager implements Signal { private SignalServiceAccountManager accountManager; private JsonGroupStore groupStore; private JsonContactsStore contactStore; + private JsonThreadStore threadStore; public Manager(String username, String settingsPath) { this.username = username; @@ -267,6 +268,13 @@ class Manager implements Signal { if (contactStore == null) { contactStore = new JsonContactsStore(); } + JsonNode threadStoreNode = rootNode.get("threadStore"); + if (threadStoreNode != null) { + threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class); + } + if (threadStore == null) { + threadStore = new JsonThreadStore(); + } } private void migrateLegacyConfigs() { @@ -305,6 +313,7 @@ class Manager implements Signal { .putPOJO("axolotlStore", signalProtocolStore) .putPOJO("groupStore", groupStore) .putPOJO("contactStore", contactStore) + .putPOJO("threadStore", threadStore) ; try { openFileChannel(); @@ -572,14 +581,17 @@ class Manager implements Signal { .build(); messageBuilder.asGroupMessage(group); } - SignalServiceDataMessage message = messageBuilder.build(); + ThreadInfo thread = threadStore.getThread(Base64.encodeBytes(groupId)); + if (thread != null) { + messageBuilder.withExpiration(thread.messageExpirationTime); + } final GroupInfo g = getGroupForSending(groupId); // Don't send group message to ourself final List membersSend = new ArrayList<>(g.members); membersSend.remove(this.username); - sendMessage(message, membersSend); + sendMessage(messageBuilder, membersSend); } public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions { @@ -587,15 +599,14 @@ class Manager implements Signal { .withId(groupId) .build(); - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asGroupMessage(group) - .build(); + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() + .asGroupMessage(group); final GroupInfo g = getGroupForSending(groupId); g.members.remove(this.username); groupStore.updateGroup(g); - sendMessage(message, g.members); + sendMessage(messageBuilder, g.members); } private static String join(CharSequence separator, Iterable list) { @@ -652,35 +663,75 @@ class Manager implements Signal { } } + if (avatarFile != null) { + createPrivateDirectories(avatarsPath); + File aFile = getGroupAvatarFile(g.groupId); + Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + groupStore.updateGroup(g); + + SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); + + // Don't send group message to ourself + final List membersSend = new ArrayList<>(g.members); + membersSend.remove(this.username); + sendMessage(messageBuilder, membersSend); + return g.groupId; + } + + private void sendUpdateGroupMessage(byte[] groupId, String recipient) throws IOException, EncapsulatedExceptions { + if (groupId == null) { + return; + } + GroupInfo g = getGroupForSending(groupId); + + if (!g.members.contains(recipient)) { + return; + } + + SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); + + // Send group message only to the recipient who requested it + final List membersSend = new ArrayList<>(); + membersSend.add(recipient); + sendMessage(messageBuilder, membersSend); + } + + private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfo g) { SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE) .withId(g.groupId) .withName(g.name) .withMembers(new ArrayList<>(g.members)); File aFile = getGroupAvatarFile(g.groupId); - if (avatarFile != null) { - createPrivateDirectories(avatarsPath); - Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } if (aFile.exists()) { try { group.withAvatar(createAttachment(aFile)); } catch (IOException e) { - throw new AttachmentInvalidException(avatarFile, e); + throw new AttachmentInvalidException(aFile.toString(), e); } } - groupStore.updateGroup(g); + return SignalServiceDataMessage.newBuilder() + .asGroupMessage(group.build()); + } - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()) - .build(); + private void sendGroupInfoRequest(byte[] groupId, String recipient) throws IOException, EncapsulatedExceptions { + if (groupId == null) { + return; + } - // Don't send group message to ourself - final List membersSend = new ArrayList<>(g.members); - membersSend.remove(this.username); - sendMessage(message, membersSend); - return g.groupId; + SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO) + .withId(groupId); + + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() + .asGroupMessage(group.build()); + + // Send group info request message to the recipient who sent us a message with this groupId + final List membersSend = new ArrayList<>(); + membersSend.add(recipient); + sendMessage(messageBuilder, membersSend); } @Override @@ -699,25 +750,22 @@ class Manager implements Signal { if (attachments != null) { messageBuilder.withAttachments(getSignalServiceAttachments(attachments)); } - SignalServiceDataMessage message = messageBuilder.build(); - - sendMessage(message, recipients); + sendMessage(messageBuilder, recipients); } @Override public void sendEndSessionMessage(List recipients) throws IOException, EncapsulatedExceptions { - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asEndSessionMessage() - .build(); + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() + .asEndSessionMessage(); - sendMessage(message, recipients); + sendMessage(messageBuilder, recipients); } 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)); try { - sendMessage(message); + sendSyncMessage(message); } catch (UntrustedIdentityException e) { e.printStackTrace(); } @@ -727,13 +775,13 @@ class Manager implements Signal { SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build(); SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); try { - sendMessage(message); + sendSyncMessage(message); } catch (UntrustedIdentityException e) { e.printStackTrace(); } } - private void sendMessage(SignalServiceSyncMessage message) + private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); @@ -745,24 +793,17 @@ class Manager implements Signal { } } - private void sendMessage(SignalServiceDataMessage message, Collection recipients) + private void sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) throws EncapsulatedExceptions, IOException { - Set recipientsTS = new HashSet<>(recipients.size()); - for (String recipient : recipients) { - try { - recipientsTS.add(getPushAddress(recipient)); - } catch (InvalidNumberException e) { - System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage()); - System.err.println("Aborting sending."); - save(); - return; - } - } + Set recipientsTS = getSignalServiceAddresses(recipients); + if (recipientsTS == null) return; + SignalServiceDataMessage message = null; try { SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + message = messageBuilder.build(); if (message.getGroupInfo().isPresent()) { try { messageSender.sendMessage(new ArrayList<>(recipientsTS), message); @@ -777,6 +818,13 @@ class Manager implements Signal { List unregisteredUsers = new LinkedList<>(); List networkExceptions = new LinkedList<>(); for (SignalServiceAddress address : recipientsTS) { + ThreadInfo thread = threadStore.getThread(address.getNumber()); + if (thread != null) { + messageBuilder.withExpiration(thread.messageExpirationTime); + } else { + messageBuilder.withExpiration(0); + } + message = messageBuilder.build(); try { messageSender.sendMessage(address, message); } catch (UntrustedIdentityException e) { @@ -793,7 +841,7 @@ class Manager implements Signal { } } } finally { - if (message.isEndSession()) { + if (message != null && message.isEndSession()) { for (SignalServiceAddress recipient : recipientsTS) { handleEndSession(recipient.getNumber()); } @@ -802,6 +850,21 @@ class Manager implements Signal { } } + private Set getSignalServiceAddresses(Collection recipients) { + Set recipientsTS = new HashSet<>(recipients.size()); + for (String recipient : recipients) { + try { + recipientsTS.add(getPushAddress(recipient)); + } catch (InvalidNumberException e) { + System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage()); + System.err.println("Aborting sending."); + save(); + return null; + } + } + return recipientsTS; + } + private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws NoSessionException, LegacyMessageException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, InvalidKeyException, InvalidKeyIdException, org.whispersystems.libsignal.UntrustedIdentityException { SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), signalProtocolStore); try { @@ -821,12 +884,13 @@ class Manager implements Signal { } private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, String source, String destination) { + String threadId; if (message.getGroupInfo().isPresent()) { SignalServiceGroup groupInfo = message.getGroupInfo().get(); + threadId = Base64.encodeBytes(groupInfo.getGroupId()); + GroupInfo group = groupStore.getGroup(groupInfo.getGroupId()); switch (groupInfo.getType()) { case UPDATE: - GroupInfo group; - group = groupStore.getGroup(groupInfo.getGroupId()); if (group == null) { group = new GroupInfo(groupInfo.getGroupId()); } @@ -853,19 +917,59 @@ class Manager implements Signal { groupStore.updateGroup(group); break; case DELIVER: + if (group == null) { + try { + sendGroupInfoRequest(groupInfo.getGroupId(), source); + } catch (IOException | EncapsulatedExceptions e) { + e.printStackTrace(); + } + } break; case QUIT: - group = groupStore.getGroup(groupInfo.getGroupId()); - if (group != null) { + if (group == null) { + try { + sendGroupInfoRequest(groupInfo.getGroupId(), source); + } catch (IOException | EncapsulatedExceptions e) { + e.printStackTrace(); + } + } else { group.members.remove(source); groupStore.updateGroup(group); } break; + case REQUEST_INFO: + if (group != null) { + try { + sendUpdateGroupMessage(groupInfo.getGroupId(), source); + } catch (IOException | EncapsulatedExceptions e) { + e.printStackTrace(); + } catch (NotAGroupMemberException e) { + // We have left this group, so don't send a group update message + } + } + break; + } + } else { + if (isSync) { + threadId = destination; + } else { + threadId = source; } } if (message.isEndSession()) { handleEndSession(isSync ? destination : source); } + if (message.isExpirationUpdate() || message.getBody().isPresent()) { + ThreadInfo thread = threadStore.getThread(threadId); + if (thread == null) { + thread = new ThreadInfo(); + thread.id = threadId; + } + if (thread.messageExpirationTime != message.getExpiresInSeconds()) { + thread.messageExpirationTime = message.getExpiresInSeconds(); + threadStore.updateThread(thread); + } + } if (message.getAttachments().isPresent()) { for (SignalServiceAttachment attachment : message.getAttachments().get()) { if (attachment.isPointer()) { @@ -1041,8 +1145,11 @@ class Manager implements Signal { DeviceContactsInputStream s = new DeviceContactsInputStream(retrieveAttachmentAsStream(syncMessage.getContacts().get().asPointer())); DeviceContact c; while ((c = s.read()) != null) { - ContactInfo contact = new ContactInfo(); - contact.number = c.getNumber(); + ContactInfo contact = contactStore.getContact(c.getNumber()); + if (contact == null) { + contact = new ContactInfo(); + contact.number = c.getNumber(); + } if (c.getName().isPresent()) { contact.name = c.getName().get(); } @@ -1217,6 +1324,7 @@ class Manager implements Signal { if (output != null) { output.close(); } + input.close(); if (!tmpFile.delete()) { System.err.println("Failed to delete temp file: " + tmpFile); } @@ -1264,13 +1372,17 @@ class Manager implements Signal { if (groupsFile.exists() && groupsFile.length() > 0) { FileInputStream contactsFileStream = new FileInputStream(groupsFile); - SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() - .withStream(contactsFileStream) - .withContentType("application/octet-stream") - .withLength(groupsFile.length()) - .build(); - - sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); + try { + SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() + .withStream(contactsFileStream) + .withContentType("application/octet-stream") + .withLength(groupsFile.length()) + .build(); + + sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); + } finally { + contactsFileStream.close(); + } } } finally { groupsFile.delete(); @@ -1299,7 +1411,7 @@ class Manager implements Signal { .withLength(contactsFile.length()) .build(); - sendMessage(SignalServiceSyncMessage.forContacts(attachmentStream)); + sendSyncMessage(SignalServiceSyncMessage.forContacts(attachmentStream)); } } finally { contactsFile.delete();