From ae39d6a0854ff11ea126b7d797206391d3170668 Mon Sep 17 00:00:00 2001 From: Mateusz Piotrowski <0mp@FreeBSD.org> Date: Mon, 28 Sep 2020 12:21:47 +0200 Subject: [PATCH 0001/1578] Bump required JRE version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb0d9d88..48ad7250 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ signal-cli is primarily intended to be used on servers to notify admins of impor ## Installation -You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well. You need to have at least JRE 8 installed, to run signal-cli. +You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well. You need to have at least JRE 11 installed, to run signal-cli. ### Install system-wide on Linux See [latest version](https://github.com/AsamK/signal-cli/releases). From a54fc92c05c5c8b532e029e82eedd73f9440e138 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 11 Oct 2020 10:41:56 +0200 Subject: [PATCH 0002/1578] Fix behavior for recipients with only UUIDs Fixes #359 --- .../signal/JsonDbusReceiveMessageHandler.java | 16 ++++++----- .../asamk/signal/ReceiveMessageHandler.java | 28 +++++++++---------- .../org/asamk/signal/json/JsonGroupInfo.java | 2 +- .../signal/json/JsonMessageEnvelope.java | 4 +-- .../signal/json/JsonSyncDataMessage.java | 2 +- .../asamk/signal/json/JsonSyncMessage.java | 2 +- .../org/asamk/signal/manager/Manager.java | 3 +- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 0728b871..5973d019 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -12,6 +12,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.ArrayList; import java.util.List; @@ -34,22 +35,23 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { conn.sendMessage(new Signal.ReceiptReceived( objectPath, envelope.getTimestamp(), - !envelope.isUnidentifiedSender() && envelope.hasSource() ? envelope.getSourceE164().get() : content.getSender().getNumber().get() + // A receipt envelope always has a source address + envelope.getSourceAddress().getLegacyIdentifier() )); } catch (DBusException e) { e.printStackTrace(); } } else if (content != null) { + final SignalServiceAddress sender = !envelope.isUnidentifiedSender() && envelope.hasSource() ? envelope.getSourceAddress() : content.getSender(); if (content.getReceiptMessage().isPresent()) { final SignalServiceReceiptMessage receiptMessage = content.getReceiptMessage().get(); if (receiptMessage.isDeliveryReceipt()) { - final String sender = !envelope.isUnidentifiedSender() && envelope.hasSource() ? envelope.getSourceE164().get() : content.getSender().getNumber().get(); for (long timestamp : receiptMessage.getTimestamps()) { try { conn.sendMessage(new Signal.ReceiptReceived( objectPath, timestamp, - sender + sender.getLegacyIdentifier() )); } catch (DBusException e) { e.printStackTrace(); @@ -66,7 +68,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { conn.sendMessage(new Signal.MessageReceived( objectPath, message.getTimestamp(), - envelope.isUnidentifiedSender() || !envelope.hasSource() ? content.getSender().getNumber().get() : envelope.getSourceE164().get(), + sender.getLegacyIdentifier(), message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent() ? message.getGroupContext().get().getGroupV1().get().getGroupId() : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", @@ -80,15 +82,15 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { if (sync_message.getSent().isPresent()) { SentTranscriptMessage transcript = sync_message.getSent().get(); - if (!envelope.isUnidentifiedSender() && envelope.hasSource() && (transcript.getDestination().isPresent() || transcript.getMessage().getGroupContext().isPresent())) { + if (transcript.getDestination().isPresent() || transcript.getMessage().getGroupContext().isPresent()) { SignalServiceDataMessage message = transcript.getMessage(); try { conn.sendMessage(new Signal.SyncMessageReceived( objectPath, transcript.getTimestamp(), - envelope.getSourceAddress().getNumber().get(), - transcript.getDestination().isPresent() ? transcript.getDestination().get().getNumber().get() : "", + sender.getLegacyIdentifier(), + transcript.getDestination().isPresent() ? transcript.getDestination().get().getLegacyIdentifier() : "", message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent() ? message.getGroupContext().get().getGroupV1().get().getGroupId() : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 9a75aa1c..cb18a8f5 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -47,8 +47,8 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) { if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { SignalServiceAddress source = envelope.getSourceAddress(); - ContactInfo sourceContact = m.getContact(source.getNumber().get()); - System.out.println(String.format("Envelope from: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + source.getNumber().get(), envelope.getSourceDevice())); + ContactInfo sourceContact = m.getContact(source.getLegacyIdentifier()); + System.out.println(String.format("Envelope from: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + source.getLegacyIdentifier(), envelope.getSourceDevice())); if (source.getRelay().isPresent()) { System.out.println("Relayed by: " + source.getRelay().get()); } @@ -76,8 +76,8 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (content == null) { System.out.println("Failed to decrypt message."); } else { - ContactInfo sourceContact = m.getContact(content.getSender().getNumber().get()); - System.out.println(String.format("Sender: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + content.getSender().getNumber().get(), content.getSenderDevice())); + ContactInfo sourceContact = m.getContact(content.getSender().getLegacyIdentifier()); + System.out.println(String.format("Sender: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + content.getSender().getLegacyIdentifier(), content.getSenderDevice())); if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); handleSignalServiceDataMessage(message); @@ -102,8 +102,8 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (syncMessage.getRead().isPresent()) { System.out.println("Received sync read messages list"); for (ReadMessage rm : syncMessage.getRead().get()) { - ContactInfo fromContact = m.getContact(rm.getSender().getNumber().get()); - System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender().getNumber().get() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp())); + ContactInfo fromContact = m.getContact(rm.getSender().getLegacyIdentifier()); + System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender().getLegacyIdentifier() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp())); } } if (syncMessage.getRequest().isPresent()) { @@ -129,14 +129,14 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final SentTranscriptMessage sentTranscriptMessage = syncMessage.getSent().get(); String to; if (sentTranscriptMessage.getDestination().isPresent()) { - String dest = sentTranscriptMessage.getDestination().get().getNumber().get(); + String dest = sentTranscriptMessage.getDestination().get().getLegacyIdentifier(); ContactInfo destContact = m.getContact(dest); to = (destContact == null ? "" : "“" + destContact.name + "” ") + dest; } else if (sentTranscriptMessage.getRecipients().size() > 0) { StringBuilder toBuilder = new StringBuilder(); for (SignalServiceAddress dest : sentTranscriptMessage.getRecipients()) { - ContactInfo destContact = m.getContact(dest.getNumber().get()); - toBuilder.append(destContact == null ? "" : "“" + destContact.name + "” ").append(dest.getNumber().get()).append(" "); + ContactInfo destContact = m.getContact(dest.getLegacyIdentifier()); + toBuilder.append(destContact == null ? "" : "“" + destContact.name + "” ").append(dest.getLegacyIdentifier()).append(" "); } to = toBuilder.toString(); } else { @@ -154,7 +154,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { System.out.println("Blocked numbers:"); final BlockedListMessage blockedList = syncMessage.getBlockedList().get(); for (SignalServiceAddress address : blockedList.getAddresses()) { - System.out.println(" - " + address.getNumber().get()); + System.out.println(" - " + address.getLegacyIdentifier()); } } if (syncMessage.getVerified().isPresent()) { @@ -178,7 +178,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (syncMessage.getViewOnceOpen().isPresent()) { final ViewOnceOpenMessage viewOnceOpenMessage = syncMessage.getViewOnceOpen().get(); System.out.println("Received sync message with view once open message:"); - System.out.println(" - Sender:" + viewOnceOpenMessage.getSender().getNumber().get()); + System.out.println(" - Sender:" + viewOnceOpenMessage.getSender().getLegacyIdentifier()); System.out.println(" - Timestamp:" + viewOnceOpenMessage.getTimestamp()); } if (syncMessage.getStickerPackOperations().isPresent()) { @@ -280,7 +280,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { System.out.println(" Type: " + groupInfo.getType()); if (groupInfo.getMembers().isPresent()) { for (SignalServiceAddress member : groupInfo.getMembers().get()) { - System.out.println(" Member: " + member.getNumber().get()); + System.out.println(" Member: " + member.getLegacyIdentifier()); } } if (groupInfo.getAvatar().isPresent()) { @@ -332,7 +332,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final SignalServiceDataMessage.Reaction reaction = message.getReaction().get(); System.out.println("Reaction:"); System.out.println(" - Emoji: " + reaction.getEmoji()); - System.out.println(" - Target author: " + reaction.getTargetAuthor().getNumber().get()); + System.out.println(" - Target author: " + reaction.getTargetAuthor().getLegacyIdentifier()); System.out.println(" - Target timestamp: " + reaction.getTargetSentTimestamp()); System.out.println(" - Is remove: " + reaction.isRemove()); } @@ -340,7 +340,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (message.getQuote().isPresent()) { SignalServiceDataMessage.Quote quote = message.getQuote().get(); System.out.println("Quote: (" + quote.getId() + ")"); - System.out.println(" Author: " + quote.getAuthor().getNumber().get()); + System.out.println(" Author: " + quote.getAuthor().getLegacyIdentifier()); System.out.println(" Text: " + quote.getText()); if (quote.getAttachments().size() > 0) { System.out.println(" Attachments: "); diff --git a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java index 572623e4..08bc19a9 100644 --- a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java +++ b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java @@ -19,7 +19,7 @@ class JsonGroupInfo { if (groupInfo.getMembers().isPresent()) { this.members = new ArrayList<>(groupInfo.getMembers().get().size()); for (SignalServiceAddress address : groupInfo.getMembers().get()) { - this.members.add(address.getNumber().get()); + this.members.add(address.getLegacyIdentifier()); } } if (groupInfo.getName().isPresent()) { diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index 3279d941..b4269949 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -20,7 +20,7 @@ public class JsonMessageEnvelope { public JsonMessageEnvelope(SignalServiceEnvelope envelope, SignalServiceContent content) { if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { SignalServiceAddress source = envelope.getSourceAddress(); - this.source = source.getNumber().get(); + this.source = source.getLegacyIdentifier(); this.relay = source.getRelay().isPresent() ? source.getRelay().get() : null; } this.sourceDevice = envelope.getSourceDevice(); @@ -28,7 +28,7 @@ public class JsonMessageEnvelope { this.isReceipt = envelope.isReceipt(); if (content != null) { if (envelope.isUnidentifiedSender()) { - this.source = content.getSender().getNumber().get(); + this.source = content.getSender().getLegacyIdentifier(); this.sourceDevice = content.getSenderDevice(); } if (content.getDataMessage().isPresent()) { diff --git a/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java index d253b197..c6571a93 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java @@ -10,7 +10,7 @@ class JsonSyncDataMessage extends JsonDataMessage { JsonSyncDataMessage(SentTranscriptMessage transcriptMessage) { super(transcriptMessage.getMessage()); if (transcriptMessage.getDestination().isPresent()) { - this.destination = transcriptMessage.getDestination().get().getNumber().get(); + this.destination = transcriptMessage.getDestination().get().getLegacyIdentifier(); } } diff --git a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java index 27766bda..31c39a3f 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java @@ -28,7 +28,7 @@ class JsonSyncMessage { if (syncMessage.getBlockedList().isPresent()) { this.blockedNumbers = new ArrayList<>(syncMessage.getBlockedList().get().getAddresses().size()); for (SignalServiceAddress address : syncMessage.getBlockedList().get().getAddresses()) { - this.blockedNumbers.add(address.getNumber().get()); + this.blockedNumbers.add(address.getLegacyIdentifier()); } } if (syncMessage.getRead().isPresent()) { diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 2ce59cdc..c332a959 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1510,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(); From 83877d69390352c5c56a05fa189108a789447d9a Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 14 Oct 2020 18:39:18 +0200 Subject: [PATCH 0003/1578] Bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0607c12b..ef9ed26f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ targetCompatibility = JavaVersion.VERSION_11 mainClassName = 'org.asamk.signal.Main' -version = '0.6.10' +version = '0.6.11' compileJava.options.encoding = 'UTF-8' From 3cbb8de6566281c2c58e21bbfe1f39b72ec9357d Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 22 Oct 2020 18:30:26 +0200 Subject: [PATCH 0004/1578] Print addition message info --- src/main/java/org/asamk/signal/ReceiveMessageHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index cb18a8f5..e417acbd 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -332,7 +332,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final SignalServiceDataMessage.Reaction reaction = message.getReaction().get(); System.out.println("Reaction:"); System.out.println(" - Emoji: " + reaction.getEmoji()); - System.out.println(" - Target author: " + reaction.getTargetAuthor().getLegacyIdentifier()); + System.out.println(" - Target author: " + reaction.getTargetAuthor().getLegacyIdentifier()); // todo resolve System.out.println(" - Target timestamp: " + reaction.getTargetSentTimestamp()); System.out.println(" - Is remove: " + reaction.isRemove()); } From 94a2da5bc1d5210ba02329816d386b5047626ee0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 19 Nov 2020 17:17:32 +0100 Subject: [PATCH 0005/1578] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index edf8a5a2..8736e619 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Setup Java JDK - uses: actions/setup-java@v1.3.0 + uses: actions/setup-java@v1 with: java-version: 11 From b0502f9f825b2fe7a76b45909b8f57abfc11c891 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 21 Nov 2020 11:47:06 +0100 Subject: [PATCH 0006/1578] Update dependencies --- build.gradle | 4 +-- .../org/asamk/signal/manager/Manager.java | 6 ++--- .../asamk/signal/manager/ServiceConfig.java | 5 ++-- .../storage/profiles/SignalProfile.java | 27 ++++++++++++++++--- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index ef9ed26f..1604def9 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,8 @@ repositories { } dependencies { - implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_14' - implementation 'org.bouncycastle:bcprov-jdk15on:1.66' + implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_15' + implementation 'org.bouncycastle:bcprov-jdk15on:1.67' implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' implementation 'com.github.hypfvieh:dbus-java:3.2.3' implementation 'org.slf4j:slf4j-nop:1.7.30' diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index c332a959..fc956266 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -431,10 +431,9 @@ public class Manager implements Closeable { private SignalServiceMessageSender getMessageSender() { // TODO implement ZkGroup support final ClientZkProfileOperations clientZkProfileOperations = null; - final boolean attachmentsV3 = false; final ExecutorService executor = null; return new SignalServiceMessageSender(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), - account.getDeviceId(), account.getSignalProtocolStore(), userAgent, account.isMultiDevice(), attachmentsV3, Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations, executor); + account.getDeviceId(), account.getSignalProtocolStore(), userAgent, account.isMultiDevice(), Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations, executor, ServiceConfig.MAX_ENVELOPE_SIZE); } private SignalServiceProfile getEncryptedRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess) throws IOException { @@ -1198,8 +1197,9 @@ public class Manager implements Closeable { SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); try { + long startTime = System.currentTimeMillis(); messageSender.sendMessage(syncMessage, unidentifiedAccess); - return SendMessageResult.success(recipient, unidentifiedAccess.isPresent(), false); + return SendMessageResult.success(recipient, unidentifiedAccess.isPresent(), false, System.currentTimeMillis() - startTime); } catch (UntrustedIdentityException e) { account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); return SendMessageResult.identityFailure(recipient, e.getIdentityKey()); diff --git a/src/main/java/org/asamk/signal/manager/ServiceConfig.java b/src/main/java/org/asamk/signal/manager/ServiceConfig.java index a8b0c5b6..4ea41734 100644 --- a/src/main/java/org/asamk/signal/manager/ServiceConfig.java +++ b/src/main/java/org/asamk/signal/manager/ServiceConfig.java @@ -1,7 +1,7 @@ package org.asamk.signal.manager; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; +import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl; @@ -26,6 +26,7 @@ public class ServiceConfig { final static int PREKEY_MINIMUM_COUNT = 20; final static int PREKEY_BATCH_SIZE = 100; final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024; + final static int MAX_ENVELOPE_SIZE = 0; final static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024; private final static String URL = "https://textsecure-service.whispersystems.org"; @@ -39,7 +40,7 @@ public class ServiceConfig { private final static String zkGroupServerPublicParamsHex = "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0="; - static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false, false); + static final AccountAttributes.Capabilities capabilities = new AccountAttributes.Capabilities(false, false, false, false); public static SignalServiceConfiguration createDefaultServiceConfiguration(String userAgent) { final Interceptor userAgentInterceptor = chain -> diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java b/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java index 71ab60e6..8c3f38b0 100644 --- a/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java +++ b/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java @@ -1,5 +1,6 @@ package org.asamk.signal.storage.profiles; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; @@ -23,7 +24,7 @@ public class SignalProfile { private final boolean unrestrictedUnidentifiedAccess; @JsonProperty - private final SignalServiceProfile.Capabilities capabilities; + private final Capabilities capabilities; public SignalProfile(final String identityKey, final String name, final File avatarFile, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess, final SignalServiceProfile.Capabilities capabilities) { this.identityKey = identityKey; @@ -31,10 +32,13 @@ public class SignalProfile { this.avatarFile = avatarFile; this.unidentifiedAccess = unidentifiedAccess; this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; - this.capabilities = capabilities; + this.capabilities = new Capabilities(); + this.capabilities.storage = capabilities.isStorage(); + this.capabilities.gv1Migration = capabilities.isGv1Migration(); + this.capabilities.gv2 = capabilities.isGv2(); } - public SignalProfile(@JsonProperty("identityKey") final String identityKey, @JsonProperty("name") final String name, @JsonProperty("unidentifiedAccess") final String unidentifiedAccess, @JsonProperty("unrestrictedUnidentifiedAccess") final boolean unrestrictedUnidentifiedAccess, @JsonProperty("capabilities") final SignalServiceProfile.Capabilities capabilities) { + public SignalProfile(@JsonProperty("identityKey") final String identityKey, @JsonProperty("name") final String name, @JsonProperty("unidentifiedAccess") final String unidentifiedAccess, @JsonProperty("unrestrictedUnidentifiedAccess") final boolean unrestrictedUnidentifiedAccess, @JsonProperty("capabilities") final Capabilities capabilities) { this.identityKey = identityKey; this.name = name; this.avatarFile = null; @@ -63,7 +67,7 @@ public class SignalProfile { return unrestrictedUnidentifiedAccess; } - public SignalServiceProfile.Capabilities getCapabilities() { + public Capabilities getCapabilities() { return capabilities; } @@ -78,4 +82,19 @@ public class SignalProfile { ", capabilities=" + capabilities + '}'; } + + public static class Capabilities { + + @JsonIgnore + public boolean uuid; + + @JsonProperty + public boolean gv2; + + @JsonProperty + public boolean storage; + + @JsonProperty + public boolean gv1Migration; + } } From 019efbe22cc4a70fc329ef592488fabff96b4105 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 21 Nov 2020 11:50:40 +0100 Subject: [PATCH 0007/1578] Show view once, remote delete and mentions info when receiving message --- .../org/asamk/signal/ReceiveMessageHandler.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index e417acbd..53cbf72b 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -259,6 +259,9 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { private void handleSignalServiceDataMessage(SignalServiceDataMessage message) { System.out.println("Message timestamp: " + DateUtils.formatTimestamp(message.getTimestamp())); + if (message.isViewOnce()) { + System.out.println("=VIEW ONCE="); + } if (message.getBody().isPresent()) { System.out.println("Body: " + message.getBody().get()); @@ -355,6 +358,18 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } + if (message.getRemoteDelete().isPresent()) { + final SignalServiceDataMessage.RemoteDelete remoteDelete = message.getRemoteDelete().get(); + System.out.println("Remote delete message: timestamp = " + remoteDelete.getTargetSentTimestamp()); + } + if (message.getMentions().isPresent()) { + final List mentions = message.getMentions().get(); + System.out.println("Mentions: "); + for (SignalServiceDataMessage.Mention mention : mentions) { + System.out.println("- " + mention.getUuid() + ": " + mention.getStart() + " (length: " + mention.getLength() + ")"); + } + } + if (message.getAttachments().isPresent()) { System.out.println("Attachments: "); for (SignalServiceAttachment attachment : message.getAttachments().get()) { From 2ab8646168a8e2bb2311d72ec621fa934ce2d247 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 21 Nov 2020 11:51:31 +0100 Subject: [PATCH 0008/1578] Don't retry messages if they fail for another reason than untrusted identity --- src/main/java/org/asamk/signal/manager/Manager.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index fc956266..add5b854 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1398,7 +1398,15 @@ public class Manager implements Closeable { if (!envelope.isReceipt()) { try { content = decryptMessage(envelope); - } catch (Exception e) { + } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { + return; + } catch (Exception er) { + // All other errors are not recoverable, so delete the cached message + try { + Files.delete(fileEntry.toPath()); + } catch (IOException e) { + System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); + } return; } List actions = handleMessage(envelope, content, ignoreAttachments); @@ -1476,6 +1484,7 @@ public class Manager implements Closeable { System.err.println("Ignoring error: " + e.getMessage()); continue; } + if (envelope.hasSource()) { // Store uuid if we don't have it already SignalServiceAddress source = envelope.getSourceAddress(); From 8c1f082c8a7b6907f3757aeb2160d6f02a92d36b Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 21 Nov 2020 11:51:59 +0100 Subject: [PATCH 0009/1578] Save account data after executing message actions Fixes #361 --- src/main/java/org/asamk/signal/manager/Manager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index add5b854..d80b522e 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1469,6 +1469,7 @@ public class Manager implements Closeable { e.printStackTrace(); } } + account.save(); queuedActions.clear(); queuedActions = null; } From 8a86f250ec7be2a37d40558c5179759b7643bf40 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 21 Nov 2020 20:11:46 +0100 Subject: [PATCH 0010/1578] Store profile keys only in profile store Fixes #328 --- .../org/asamk/signal/manager/Manager.java | 59 +++++++++---------- .../signal/storage/contacts/ContactInfo.java | 4 +- .../signal/storage/profiles/ProfileStore.java | 24 +++++++- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index d80b522e..6b2e7c96 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -267,6 +267,21 @@ public class Manager implements Closeable { account.setProfileKey(KeyUtils.createProfileKey()); account.save(); } + // Store profile keys only in profile store + for (ContactInfo contact : account.getContactStore().getContacts()) { + String profileKeyString = contact.profileKey; + if (profileKeyString == null) { + continue; + } + final ProfileKey profileKey; + try { + profileKey = new ProfileKey(Base64.decode(profileKeyString)); + } catch (InvalidInputException | IOException e) { + continue; + } + contact.profileKey = null; + account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey); + } } public void checkAccountState() throws IOException { @@ -993,16 +1008,10 @@ public class Manager implements Closeable { } private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { - ContactInfo contact = account.getContactStore().getContact(recipient); - if (contact == null || contact.profileKey == null) { + ProfileKey theirProfileKey = account.getProfileStore().getProfileKey(recipient); + if (theirProfileKey == null) { return null; } - ProfileKey theirProfileKey; - try { - theirProfileKey = new ProfileKey(Base64.decode(contact.profileKey)); - } catch (InvalidInputException | IOException e) { - throw new AssertionError(e); - } SignalProfile targetProfile; try { targetProfile = getRecipientProfile(recipient, Optional.absent(), theirProfileKey); @@ -1326,24 +1335,16 @@ public class Manager implements Closeable { } } if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { - if (source.matches(account.getSelfAddress())) { - try { - this.account.setProfileKey(new ProfileKey(message.getProfileKey().get())); - } catch (InvalidInputException ignored) { - } - ContactInfo contact = account.getContactStore().getContact(source); - if (contact != null) { - contact.profileKey = Base64.encodeBytes(message.getProfileKey().get()); - account.getContactStore().updateContact(contact); - } - } else { - ContactInfo contact = account.getContactStore().getContact(source); - if (contact == null) { - contact = new ContactInfo(source); - } - contact.profileKey = Base64.encodeBytes(message.getProfileKey().get()); - account.getContactStore().updateContact(contact); + final ProfileKey profileKey; + try { + profileKey = new ProfileKey(message.getProfileKey().get()); + } catch (InvalidInputException e) { + throw new AssertionError(e); } + if (source.matches(account.getSelfAddress())) { + this.account.setProfileKey(profileKey); + } + this.account.getProfileStore().storeProfileKey(source, profileKey); } if (message.getPreviews().isPresent()) { final List previews = message.getPreviews().get(); @@ -1690,7 +1691,7 @@ public class Manager implements Closeable { contact.color = c.getColor().get(); } if (c.getProfileKey().isPresent()) { - contact.profileKey = Base64.encodeBytes(c.getProfileKey().get().serialize()); + account.getProfileStore().storeProfileKey(address, c.getProfileKey().get()); } if (c.getVerified().isPresent()) { final VerifiedMessage verifiedMessage = c.getVerified().get(); @@ -1874,11 +1875,7 @@ public class Manager implements Closeable { verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime()); } - ProfileKey profileKey = null; - try { - profileKey = record.profileKey == null ? null : new ProfileKey(Base64.decode(record.profileKey)); - } catch (InvalidInputException ignored) { - } + ProfileKey profileKey = account.getProfileStore().getProfileKey(record.getAddress()); out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name), createContactAvatarAttachment(record.number), Optional.fromNullable(record.color), Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked, diff --git a/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java b/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java index 4d3a5e95..3b155210 100644 --- a/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java +++ b/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java @@ -7,6 +7,8 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.UUID; +import static com.fasterxml.jackson.annotation.JsonProperty.Access.WRITE_ONLY; + public class ContactInfo { @JsonProperty @@ -24,7 +26,7 @@ public class ContactInfo { @JsonProperty(defaultValue = "0") public int messageExpirationTime; - @JsonProperty + @JsonProperty(access = WRITE_ONLY) public String profileKey; @JsonProperty(defaultValue = "false") diff --git a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java b/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java index 24b08968..662d0161 100644 --- a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java +++ b/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java @@ -20,7 +20,6 @@ import org.whispersystems.util.Base64; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -42,6 +41,15 @@ public class ProfileStore { return null; } + public ProfileKey getProfileKey(SignalServiceAddress serviceAddress) { + for (SignalProfileEntry entry : profiles) { + if (entry.getServiceAddress().matches(serviceAddress)) { + return entry.getProfileKey(); + } + } + return null; + } + public void updateProfile(SignalServiceAddress serviceAddress, ProfileKey profileKey, long now, SignalProfile profile) { SignalProfileEntry newEntry = new SignalProfileEntry(serviceAddress, profileKey, now, profile); for (int i = 0; i < profiles.size(); i++) { @@ -54,6 +62,20 @@ public class ProfileStore { profiles.add(newEntry); } + public void storeProfileKey(SignalServiceAddress serviceAddress, ProfileKey profileKey) { + SignalProfileEntry newEntry = new SignalProfileEntry(serviceAddress, profileKey, 0, null); + for (int i = 0; i < profiles.size(); i++) { + if (profiles.get(i).getServiceAddress().matches(serviceAddress)) { + if (!profiles.get(i).getProfileKey().equals(profileKey)) { + profiles.set(i, newEntry); + } + return; + } + } + + profiles.add(newEntry); + } + public static class ProfileStoreDeserializer extends JsonDeserializer> { @Override From d94a7511ddabe0ae19ec853e9422e0c52ae4f7c1 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 10:03:41 +0100 Subject: [PATCH 0011/1578] Use StandardCharsets.UTF_8 --- .../org/asamk/signal/manager/Manager.java | 3 ++- .../java/org/asamk/signal/manager/Utils.java | 23 ++++--------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 6b2e7c96..a4127b75 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -124,6 +124,7 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @@ -857,7 +858,7 @@ public class Manager implements Closeable { String packId = messageSender.uploadStickerManifest(manifest, packKey); try { - return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder.encode(packId, "utf-8") + "&pack_key=" + URLEncoder.encode(Hex.toStringCondensed(packKey), "utf-8")) + return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder.encode(packId, StandardCharsets.UTF_8) + "&pack_key=" + URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)) .toString(); } catch (URISyntaxException e) { throw new AssertionError(e); diff --git a/src/main/java/org/asamk/signal/manager/Utils.java b/src/main/java/org/asamk/signal/manager/Utils.java index 05fcfb5e..466cbcc3 100644 --- a/src/main/java/org/asamk/signal/manager/Utils.java +++ b/src/main/java/org/asamk/signal/manager/Utils.java @@ -27,11 +27,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLConnection; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; @@ -107,31 +107,16 @@ class Utils { String[] params = query.split("&"); Map map = new HashMap<>(); for (String param : params) { - String name = null; final String[] paramParts = param.split("="); - try { - name = URLDecoder.decode(paramParts[0], "utf-8"); - } catch (UnsupportedEncodingException e) { - // Impossible - } - String value = null; - try { - value = URLDecoder.decode(paramParts[1], "utf-8"); - } catch (UnsupportedEncodingException e) { - // Impossible - } + String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8); + String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8); map.put(name, value); } return map; } static String createDeviceLinkUri(DeviceLinkInfo info) { - try { - return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8"); - } catch (UnsupportedEncodingException e) { - // Shouldn't happen - return null; - } + return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, StandardCharsets.UTF_8) + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), StandardCharsets.UTF_8); } static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException { From a634b46eb23a91adcaebd8bb4d15fe4d6b67fa52 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 10:38:28 +0100 Subject: [PATCH 0012/1578] Remove legacy EncapsulatedExceptions from manager --- .../signal/commands/QuitGroupCommand.java | 13 +- .../signal/commands/SendReactionCommand.java | 15 +-- .../org/asamk/signal/dbus/DbusSignalImpl.java | 61 ++++----- .../org/asamk/signal/manager/Manager.java | 116 ++++++------------ .../org/asamk/signal/util/ErrorUtils.java | 44 +++++-- 5 files changed, 108 insertions(+), 141 deletions(-) diff --git a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java index 6db230f5..6d0edf87 100644 --- a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java @@ -8,16 +8,18 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotAGroupMemberException; import org.asamk.signal.util.GroupIdFormatException; import org.asamk.signal.util.Util; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.signalservice.api.messages.SendMessageResult; import java.io.IOException; +import java.util.List; import static org.asamk.signal.util.ErrorUtils.handleAssertionError; -import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions; import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException; import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException; import static org.asamk.signal.util.ErrorUtils.handleIOException; import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException; +import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults; public class QuitGroupCommand implements LocalCommand { @@ -36,14 +38,11 @@ public class QuitGroupCommand implements LocalCommand { } try { - m.sendQuitGroupMessage(Util.decodeGroupId(ns.getString("group"))); - return 0; + final Pair> results = m.sendQuitGroupMessage(Util.decodeGroupId(ns.getString("group"))); + return handleTimestampAndSendMessageResults(results.first(), results.second()); } catch (IOException e) { handleIOException(e); return 3; - } catch (EncapsulatedExceptions e) { - handleEncapsulatedExceptions(e); - return 3; } catch (AssertionError e) { handleAssertionError(e); return 1; diff --git a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java index 7e748866..3d000a62 100644 --- a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java @@ -9,18 +9,20 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotAGroupMemberException; import org.asamk.signal.util.GroupIdFormatException; import org.asamk.signal.util.Util; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; +import java.util.List; import static org.asamk.signal.util.ErrorUtils.handleAssertionError; -import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions; import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException; import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException; import static org.asamk.signal.util.ErrorUtils.handleIOException; import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException; import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException; +import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults; public class SendReactionCommand implements LocalCommand { @@ -66,19 +68,18 @@ public class SendReactionCommand implements LocalCommand { long targetTimestamp = ns.getLong("target_timestamp"); try { + final Pair> results; if (ns.getString("group") != null) { byte[] groupId = Util.decodeGroupId(ns.getString("group")); - m.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId); + results = m.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId); } else { - m.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, ns.getList("recipient")); + results = m.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, ns.getList("recipient")); } + handleTimestampAndSendMessageResults(results.first(), results.second()); return 0; } catch (IOException e) { handleIOException(e); return 3; - } catch (EncapsulatedExceptions e) { - handleEncapsulatedExceptions(e); - return 3; } catch (AssertionError e) { handleAssertionError(e); return 1; diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 17cc2caa..bffde498 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -6,11 +6,10 @@ import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotAGroupMemberException; import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.util.ErrorUtils; import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; -import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; @@ -43,41 +42,28 @@ public class DbusSignalImpl implements Signal { return sendMessage(message, attachments, recipients); } - private static DBusExecutionException convertEncapsulatedExceptions(EncapsulatedExceptions e) { - if (e.getNetworkExceptions().size() + e.getUnregisteredUserExceptions().size() + e.getUntrustedIdentityExceptions().size() == 1) { - if (e.getNetworkExceptions().size() == 1) { - NetworkFailureException n = e.getNetworkExceptions().get(0); - return new Error.Failure("Network failure for \"" + n.getE164number() + "\": " + n.getMessage()); - } else if (e.getUnregisteredUserExceptions().size() == 1) { - UnregisteredUserException n = e.getUnregisteredUserExceptions().get(0); - return new Error.UnregisteredUser("Unregistered user \"" + n.getE164Number() + "\": " + n.getMessage()); - } else if (e.getUntrustedIdentityExceptions().size() == 1) { - UntrustedIdentityException n = e.getUntrustedIdentityExceptions().get(0); - return new Error.UntrustedIdentity("Untrusted Identity for \"" + n.getIdentifier() + "\": " + n.getMessage()); - } + private static void checkSendMessageResults(long timestamp, List results) throws DBusExecutionException { + List errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results); + if (errors.size() == 0) { + return; } StringBuilder message = new StringBuilder(); - message.append("Failed to send (some) messages:").append('\n'); - for (NetworkFailureException n : e.getNetworkExceptions()) { - message.append("Network failure for \"").append(n.getE164number()).append("\": ").append(n.getMessage()).append('\n'); - } - for (UnregisteredUserException n : e.getUnregisteredUserExceptions()) { - message.append("Unregistered user \"").append(n.getE164Number()).append("\": ").append(n.getMessage()).append('\n'); - } - for (UntrustedIdentityException n : e.getUntrustedIdentityExceptions()) { - message.append("Untrusted Identity for \"").append(n.getIdentifier()).append("\": ").append(n.getMessage()).append('\n'); + message.append(timestamp).append('\n'); + message.append("Failed to send (some) messages:\n"); + for (String error : errors) { + message.append(error).append('\n'); } - return new Error.Failure(message.toString()); + throw new Error.Failure(message.toString()); } @Override public long sendMessage(final String message, final List attachments, final List recipients) { try { - return m.sendMessage(message, attachments, recipients); - } catch (EncapsulatedExceptions e) { - throw convertEncapsulatedExceptions(e); + final Pair> results = m.sendMessage(message, attachments, recipients); + checkSendMessageResults(results.first(), results.second()); + return results.first(); } catch (InvalidNumberException e) { throw new Error.InvalidNumber(e.getMessage()); } catch (AttachmentInvalidException e) { @@ -90,11 +76,10 @@ public class DbusSignalImpl implements Signal { @Override public void sendEndSessionMessage(final List recipients) { try { - m.sendEndSessionMessage(recipients); + final Pair> results = m.sendEndSessionMessage(recipients); + checkSendMessageResults(results.first(), results.second()); } catch (IOException e) { throw new Error.Failure(e.getMessage()); - } catch (EncapsulatedExceptions e) { - throw convertEncapsulatedExceptions(e); } catch (InvalidNumberException e) { throw new Error.InvalidNumber(e.getMessage()); } @@ -103,11 +88,11 @@ public class DbusSignalImpl implements Signal { @Override public long sendGroupMessage(final String message, final List attachments, final byte[] groupId) { try { - return m.sendGroupMessage(message, attachments, groupId); + Pair> results = m.sendGroupMessage(message, attachments, groupId); + checkSendMessageResults(results.first(), results.second()); + return results.first(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); - } catch (EncapsulatedExceptions e) { - throw convertEncapsulatedExceptions(e); } catch (GroupNotFoundException | NotAGroupMemberException e) { throw new Error.GroupNotFound(e.getMessage()); } catch (AttachmentInvalidException e) { @@ -184,11 +169,11 @@ public class DbusSignalImpl implements Signal { @Override public byte[] updateGroup(final byte[] groupId, final String name, final List members, final String avatar) { try { - return m.updateGroup(groupId, name, members, avatar); + final Pair> results = m.updateGroup(groupId, name, members, avatar); + checkSendMessageResults(0, results.second()); + return results.first(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); - } catch (EncapsulatedExceptions e) { - throw convertEncapsulatedExceptions(e); } catch (GroupNotFoundException | NotAGroupMemberException e) { throw new Error.GroupNotFound(e.getMessage()); } catch (InvalidNumberException e) { diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index a4127b75..63c6ee7d 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -96,10 +96,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; -import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; -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; @@ -134,7 +131,6 @@ 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.Objects; @@ -161,7 +157,7 @@ public class Manager implements Closeable { private SignalServiceAccountManager accountManager; private SignalServiceMessagePipe messagePipe = null; private SignalServiceMessagePipe unidentifiedMessagePipe = null; - private boolean discoverableByPhoneNumber = true; + private final boolean discoverableByPhoneNumber = true; public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) { this.account = account; @@ -540,9 +536,12 @@ public class Manager implements Closeable { return account.getGroupStore().getGroups(); } - public long sendGroupMessage(String messageText, List attachments, - byte[] groupId) - throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { + public Pair> sendGroupMessage( + String messageText, + List attachments, + byte[] groupId + ) + throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); if (attachments != null) { messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments)); @@ -558,12 +557,12 @@ public class Manager implements Closeable { messageBuilder.withExpiration(g.messageExpirationTime); - return sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - public void sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor, - long targetSentTimestamp, byte[] groupId) - throws IOException, EncapsulatedExceptions, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { + public Pair> sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor, + long targetSentTimestamp, byte[] groupId) + throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp); final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withReaction(reaction); @@ -574,10 +573,10 @@ public class Manager implements Closeable { messageBuilder.asGroupMessage(group); } final GroupInfo g = getGroupForSending(groupId); - sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, NotAGroupMemberException { + public Pair> sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException { SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT) .withId(groupId) .build(); @@ -589,10 +588,10 @@ public class Manager implements Closeable { g.removeMember(account.getSelfAddress()); account.getGroupStore().updateGroup(g); - sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { + private Pair> sendUpdateGroupMessage(byte[] groupId, String name, Collection members, String avatarFile) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { GroupInfo g; if (groupId == null) { // Create new group @@ -637,24 +636,21 @@ public class Manager implements Closeable { SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); - sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); - return g.groupId; + final Pair> result = sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return new Pair<>(g.groupId, result.second()); } - void sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { - if (groupId == null) { - return; - } + Pair> sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { GroupInfo g = getGroupForSending(groupId); if (!g.isMember(recipient)) { - return; + throw new NotAGroupMemberException(groupId, g.name); } SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); // Send group message only to the recipient who requested it - sendMessageLegacy(messageBuilder, Collections.singleton(recipient)); + return sendMessage(messageBuilder, Collections.singleton(recipient)); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfo g) throws AttachmentInvalidException { @@ -677,11 +673,7 @@ public class Manager implements Closeable { .withExpiration(g.messageExpirationTime); } - void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions { - if (groupId == null) { - return; - } - + Pair> sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException { SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO) .withId(groupId); @@ -689,7 +681,7 @@ public class Manager implements Closeable { .asGroupMessage(group.build()); // Send group info request message to the recipient who sent us a message with this groupId - sendMessageLegacy(messageBuilder, Collections.singleton(recipient)); + return sendMessage(messageBuilder, Collections.singleton(recipient)); } void sendReceipt(SignalServiceAddress remoteAddress, long messageId) throws IOException, UntrustedIdentityException { @@ -700,9 +692,9 @@ public class Manager implements Closeable { getMessageSender().sendReceipt(remoteAddress, getAccessFor(remoteAddress), receiptMessage); } - public long sendMessage(String messageText, List attachments, - List recipients) - throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException { + public Pair> sendMessage(String messageText, List attachments, + List recipients) + throws IOException, AttachmentInvalidException, InvalidNumberException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); if (attachments != null) { List attachmentStreams = Utils.getSignalServiceAttachments(attachments); @@ -720,25 +712,25 @@ public class Manager implements Closeable { messageBuilder.withAttachments(attachmentPointers); } - return sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); + return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); } - public void sendMessageReaction(String emoji, boolean remove, String targetAuthor, - long targetSentTimestamp, List recipients) - throws IOException, EncapsulatedExceptions, InvalidNumberException { + public Pair> sendMessageReaction(String emoji, boolean remove, String targetAuthor, + long targetSentTimestamp, List recipients) + throws IOException, InvalidNumberException { SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp); final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withReaction(reaction); - sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); + return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); } - public void sendEndSessionMessage(List recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException { + public Pair> sendEndSessionMessage(List recipients) throws IOException, InvalidNumberException { SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asEndSessionMessage(); final Collection signalServiceAddresses = getSignalServiceAddresses(recipients); try { - sendMessageLegacy(messageBuilder, signalServiceAddresses); + return sendMessage(messageBuilder, signalServiceAddresses); } catch (Exception e) { for (SignalServiceAddress address : signalServiceAddresses) { handleEndSession(address); @@ -793,7 +785,7 @@ public class Manager implements Closeable { account.save(); } - public byte[] updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { + public Pair> updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { if (groupId.length == 0) { groupId = null; } @@ -1098,34 +1090,6 @@ public class Manager implements Closeable { } } - /** - * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult. - */ - private long sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) - throws EncapsulatedExceptions, IOException { - final long timestamp = System.currentTimeMillis(); - messageBuilder.withTimestamp(timestamp); - List results = sendMessage(messageBuilder, recipients); - - List untrustedIdentities = new LinkedList<>(); - List unregisteredUsers = new LinkedList<>(); - List networkExceptions = new LinkedList<>(); - - for (SendMessageResult result : results) { - if (result.isUnregisteredFailure()) { - unregisteredUsers.add(new UnregisteredUserException(result.getAddress().getLegacyIdentifier(), null)); - } else if (result.isNetworkFailure()) { - networkExceptions.add(new NetworkFailureException(result.getAddress().getLegacyIdentifier(), null)); - } else if (result.getIdentityFailure() != null) { - untrustedIdentities.add(new UntrustedIdentityException("Untrusted", result.getAddress().getLegacyIdentifier(), result.getIdentityFailure().getIdentityKey())); - } - } - if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) { - throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions); - } - return timestamp; - } - private Collection getSignalServiceAddresses(Collection numbers) throws InvalidNumberException { final Set signalServiceAddresses = new HashSet<>(numbers.size()); @@ -1135,8 +1099,10 @@ public class Manager implements Closeable { return signalServiceAddresses; } - private List sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) + private Pair> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) throws IOException { + final long timestamp = System.currentTimeMillis(); + messageBuilder.withTimestamp(timestamp); if (messagePipe == null) { messagePipe = getMessageReceiver().createMessagePipe(); } @@ -1156,10 +1122,10 @@ public class Manager implements Closeable { account.getSignalProtocolStore().saveIdentity(r.getAddress(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED); } } - return result; + return new Pair<>(timestamp, result); } catch (UntrustedIdentityException e) { account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); - return Collections.emptyList(); + return new Pair<>(timestamp, Collections.emptyList()); } } else { // Send to all individually, so sync messages are sent correctly @@ -1180,7 +1146,7 @@ public class Manager implements Closeable { results.add(sendMessage(address, message)); } } - return results; + return new Pair<>(timestamp, results); } } finally { if (message != null && message.isEndSession()) { @@ -1553,9 +1519,7 @@ public class Manager implements Closeable { if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) { SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); GroupInfo group = getGroup(groupInfo.getGroupId()); - if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.blocked) { - return true; - } + return groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.blocked; } } return false; diff --git a/src/main/java/org/asamk/signal/util/ErrorUtils.java b/src/main/java/org/asamk/signal/util/ErrorUtils.java index a09be3d0..37237f63 100644 --- a/src/main/java/org/asamk/signal/util/ErrorUtils.java +++ b/src/main/java/org/asamk/signal/util/ErrorUtils.java @@ -2,13 +2,12 @@ package org.asamk.signal.util; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.NotAGroupMemberException; -import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; -import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class ErrorUtils { @@ -21,17 +20,36 @@ public class ErrorUtils { System.err.println("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README"); } - public static void handleEncapsulatedExceptions(EncapsulatedExceptions e) { + public static int handleTimestampAndSendMessageResults(long timestamp, List results) { + System.out.println(timestamp); + List errors = getErrorMessagesFromSendMessageResults(results); + return handleSendMessageResultErrors(errors); + } + + public static List getErrorMessagesFromSendMessageResults(List results) { + List errors = new ArrayList<>(); + for (SendMessageResult result : results) { + if (result.isNetworkFailure()) { + errors.add(String.format("Network failure for \"%s\"", result.getAddress().getLegacyIdentifier())); + } else if (result.isUnregisteredFailure()) { + errors.add(String.format("Unregistered user \"%s\"", result.getAddress().getLegacyIdentifier())); + } else if (result.getIdentityFailure() != null) { + errors.add(String.format("Untrusted Identity for \"%s\"", result.getAddress().getLegacyIdentifier())); + } + } + + return errors; + } + + private static int handleSendMessageResultErrors(List errors) { + if (errors.size() == 0) { + return 0; + } System.err.println("Failed to send (some) messages:"); - for (NetworkFailureException n : e.getNetworkExceptions()) { - System.err.println("Network failure for \"" + n.getE164number() + "\": " + n.getMessage()); - } - for (UnregisteredUserException n : e.getUnregisteredUserExceptions()) { - System.err.println("Unregistered user \"" + n.getE164Number() + "\": " + n.getMessage()); - } - for (UntrustedIdentityException n : e.getUntrustedIdentityExceptions()) { - System.err.println("Untrusted Identity for \"" + n.getIdentifier() + "\": " + n.getMessage()); + for (String error : errors) { + System.err.println(error); } + return 3; } public static void handleIOException(IOException e) { From 44851887894ed9162006ad9b12309e732aa3e876 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 10:48:42 +0100 Subject: [PATCH 0013/1578] Adapt json output to always use receiptMessage and remove isReceipt field Fixes #346 --- .../org/asamk/signal/json/JsonMessageEnvelope.java | 9 ++++++--- .../org/asamk/signal/json/JsonReceiptMessage.java | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index b4269949..5e5e6a33 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -5,13 +5,14 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import java.util.List; + public class JsonMessageEnvelope { String source; int sourceDevice; String relay; long timestamp; - boolean isReceipt; JsonDataMessage dataMessage; JsonSyncMessage syncMessage; JsonCallMessage callMessage; @@ -25,7 +26,9 @@ public class JsonMessageEnvelope { } this.sourceDevice = envelope.getSourceDevice(); this.timestamp = envelope.getTimestamp(); - this.isReceipt = envelope.isReceipt(); + if (envelope.isReceipt()) { + this.receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp)); + } if (content != null) { if (envelope.isUnidentifiedSender()) { this.source = content.getSender().getLegacyIdentifier(); @@ -55,7 +58,7 @@ public class JsonMessageEnvelope { public JsonMessageEnvelope(Signal.ReceiptReceived receiptReceived) { source = receiptReceived.getSender(); timestamp = receiptReceived.getTimestamp(); - isReceipt = true; + receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp)); } public JsonMessageEnvelope(Signal.SyncMessageReceived messageReceived) { diff --git a/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java b/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java index 1b896053..b2ab7f75 100644 --- a/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java @@ -22,4 +22,15 @@ class JsonReceiptMessage { } this.timestamps = receiptMessage.getTimestamps(); } + + private JsonReceiptMessage(final long when, final boolean isDelivery, final boolean isRead, final List timestamps) { + this.when = when; + this.isDelivery = isDelivery; + this.isRead = isRead; + this.timestamps = timestamps; + } + + static JsonReceiptMessage deliveryReceipt(final long when, final List timestamps) { + return new JsonReceiptMessage(when, true, false, timestamps); + } } From c3e1d4fc750998a946a5c5b60d705e5eb582afd9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 11:03:14 +0100 Subject: [PATCH 0014/1578] Add possibility to pass a captcha token to register command Fixes #251 --- man/signal-cli.1.adoc | 6 ++++++ .../java/org/asamk/signal/commands/RegisterCommand.java | 8 ++++++-- src/main/java/org/asamk/signal/manager/Manager.java | 6 +++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 98a5da2a..0bef0afc 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -54,6 +54,12 @@ Use the verify command to complete the verification. *-v*, *--voice*:: The verification should be done over voice, not SMS. +*--captcha*:: +The captcha token, required if registration failed with a captcha required error. +To get the token, go to https://signalcaptchas.org/registration/generate.html +Check the developer tools for a redirect starting with signalcaptcha:// +Everything after signalcaptcha:// is the captcha token. + === verify Verify the number using the code received via SMS or voice. diff --git a/src/main/java/org/asamk/signal/commands/RegisterCommand.java b/src/main/java/org/asamk/signal/commands/RegisterCommand.java index e95487bf..f69e0844 100644 --- a/src/main/java/org/asamk/signal/commands/RegisterCommand.java +++ b/src/main/java/org/asamk/signal/commands/RegisterCommand.java @@ -16,15 +16,19 @@ public class RegisterCommand implements LocalCommand { subparser.addArgument("-v", "--voice") .help("The verification should be done over voice, not sms.") .action(Arguments.storeTrue()); + subparser.addArgument("--captcha") + .help("The captcha token, required if registration failed with a captcha required error."); } @Override public int handleCommand(final Namespace ns, final Manager m) { try { - m.register(ns.getBoolean("voice")); + final boolean voiceVerification = ns.getBoolean("voice"); + final String captcha = ns.getString("captcha"); + m.register(voiceVerification, captcha); return 0; } catch (CaptchaRequiredException e) { - System.err.println("Captcha required for verification (" + e.getMessage() + ")"); + System.err.println("Captcha invalid or required for verification (" + e.getMessage() + ")"); return 1; } catch (IOException e) { System.err.println("Request verify error: " + e.getMessage()); diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 63c6ee7d..83a3926a 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -298,7 +298,7 @@ public class Manager implements Closeable { return account.isRegistered(); } - public void register(boolean voiceVerification) throws IOException { + public void register(boolean voiceVerification, String captcha) throws IOException { account.setPassword(KeyUtils.createPassword()); // Resetting UUID, because registering doesn't work otherwise @@ -306,9 +306,9 @@ public class Manager implements Closeable { accountManager = createSignalServiceAccountManager(); if (voiceVerification) { - accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent()); + accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.fromNullable(captcha), Optional.absent()); } else { - accountManager.requestSmsVerificationCode(false, Optional.absent(), Optional.absent()); + accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent()); } account.setRegistered(false); From 943b2c7304add49111c1098eaa70b82b8f9344bb Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 12:56:32 +0100 Subject: [PATCH 0015/1578] Add output for new message infos --- .../asamk/signal/ReceiveMessageHandler.java | 81 ++++++++++++++----- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 53cbf72b..0a264432 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -11,6 +11,8 @@ 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.SignalServiceGroupContext; +import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; @@ -22,6 +24,8 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMess import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage; import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; +import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage; +import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; @@ -170,6 +174,15 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (configurationMessage.getReadReceipts().isPresent()) { System.out.println(" - Read receipts: " + (configurationMessage.getReadReceipts().get() ? "enabled" : "disabled")); } + if (configurationMessage.getLinkPreviews().isPresent()) { + System.out.println(" - Link previews: " + (configurationMessage.getLinkPreviews().get() ? "enabled" : "disabled")); + } + if (configurationMessage.getTypingIndicators().isPresent()) { + System.out.println(" - Typing indicators: " + (configurationMessage.getTypingIndicators().get() ? "enabled" : "disabled")); + } + if (configurationMessage.getUnidentifiedDeliveryIndicators().isPresent()) { + System.out.println(" - Unidentified Delivery Indicators: " + (configurationMessage.getUnidentifiedDeliveryIndicators().get() ? "enabled" : "disabled")); + } } if (syncMessage.getFetchType().isPresent()) { final SignalServiceSyncMessage.FetchType fetchType = syncMessage.getFetchType().get(); @@ -194,6 +207,26 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } } + if (syncMessage.getMessageRequestResponse().isPresent()) { + final MessageRequestResponseMessage requestResponseMessage = syncMessage.getMessageRequestResponse().get(); + System.out.println("Received message request response:"); + System.out.println(" Type: " + requestResponseMessage.getType()); + if (requestResponseMessage.getGroupId().isPresent()) { + System.out.println(" Group id: " + Base64.encodeBytes(requestResponseMessage.getGroupId().get())); + } + if (requestResponseMessage.getPerson().isPresent()) { + System.out.println(" Person: " + requestResponseMessage.getPerson().get().getLegacyIdentifier()); + } + } + if (syncMessage.getKeys().isPresent()) { + final KeysMessage keysMessage = syncMessage.getKeys().get(); + System.out.println("Received sync message with keys:"); + if (keysMessage.getStorageService().isPresent()) { + System.out.println(" With storage key length: " + keysMessage.getStorageService().get().serialize().length); + } else { + System.out.println(" With empty storage key"); + } + } } if (content.getCallMessage().isPresent()) { System.out.println("Received a call message"); @@ -266,34 +299,42 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (message.getBody().isPresent()) { System.out.println("Body: " + message.getBody().get()); } - if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) { - SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); + if (message.getGroupContext().isPresent()) { System.out.println("Group info:"); - System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId())); - if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) { - System.out.println(" Name: " + groupInfo.getName().get()); - } else { - GroupInfo group = m.getGroup(groupInfo.getGroupId()); - if (group != null) { - System.out.println(" Name: " + group.name); + final SignalServiceGroupContext groupContext = message.getGroupContext().get(); + if (groupContext.getGroupV1().isPresent()) { + SignalServiceGroup groupInfo = groupContext.getGroupV1().get(); + System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId())); + if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) { + System.out.println(" Name: " + groupInfo.getName().get()); } else { - System.out.println(" Name: "); + GroupInfo group = m.getGroup(groupInfo.getGroupId()); + if (group != null) { + System.out.println(" Name: " + group.name); + } else { + System.out.println(" Name: "); + } } - } - System.out.println(" Type: " + groupInfo.getType()); - if (groupInfo.getMembers().isPresent()) { - for (SignalServiceAddress member : groupInfo.getMembers().get()) { - System.out.println(" Member: " + member.getLegacyIdentifier()); + System.out.println(" Type: " + groupInfo.getType()); + if (groupInfo.getMembers().isPresent()) { + for (SignalServiceAddress member : groupInfo.getMembers().get()) { + System.out.println(" Member: " + member.getLegacyIdentifier()); + } } - } - if (groupInfo.getAvatar().isPresent()) { - System.out.println(" Avatar:"); - printAttachment(groupInfo.getAvatar().get()); + if (groupInfo.getAvatar().isPresent()) { + System.out.println(" Avatar:"); + printAttachment(groupInfo.getAvatar().get()); + } + } else if (groupContext.getGroupV2().isPresent()) { + final SignalServiceGroupV2 groupInfo = groupContext.getGroupV2().get(); + System.out.println(" Revision: " + groupInfo.getRevision()); + System.out.println(" Master key length: " + groupInfo.getMasterKey().serialize().length); + System.out.println(" Has signed group change: " + groupInfo.hasSignedGroupChange()); } } if (message.getPreviews().isPresent()) { final List previews = message.getPreviews().get(); - System.out.println("Previes:"); + System.out.println("Previews:"); for (SignalServiceDataMessage.Preview preview : previews) { System.out.println(" - Title: " + preview.getTitle()); System.out.println(" - Url: " + preview.getUrl()); From 4c3a249a2cbac6ec45ae9523b941d98914e0b1ea Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 15:49:42 +0100 Subject: [PATCH 0016/1578] Add CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..63b52474 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## [Unreleased] +### Added +- Show additional message content (view once, remote delete, mention, …) for received messages +- `--captcha` parameter for `register` command, required for some IP ranges + +### Changed +- Profile keys are now stored separately from contact list +- Receipts from normal and unidentified messages now have the same format in json output + +### Fixed +- Issue where some messages were sent with an old counter index + +## Older + +Look at the [release tags](https://github.com/AsamK/signal-cli/releases) for information about older releases. From 9fff8f5b14a05c18324275c043be3803684ef328 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Nov 2020 15:50:34 +0100 Subject: [PATCH 0017/1578] Bump version --- CHANGELOG.md | 2 ++ build.gradle | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b52474..5fc25c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [0.6.12] - 2020-11-22 ### Added - Show additional message content (view once, remote delete, mention, …) for received messages - `--captcha` parameter for `register` command, required for some IP ranges diff --git a/build.gradle b/build.gradle index 1604def9..05015987 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ targetCompatibility = JavaVersion.VERSION_11 mainClassName = 'org.asamk.signal.Main' -version = '0.6.11' +version = '0.6.12' compileJava.options.encoding = 'UTF-8' From 6d016bcfc97c49deb76fa2c3a07ce62aee4ca8d8 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 23 Nov 2020 22:40:05 +0100 Subject: [PATCH 0018/1578] Update codestyle --- .idea/codeStyles/Project.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 4953eaca..a9284fc3 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -28,6 +28,24 @@