From e04c45766d50d6a4e2d3c084145e2115b38bef5c Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 3 Apr 2020 14:27:18 +0200 Subject: [PATCH 01/16] Rename fingerprint to safety number Fixes #92 --- man/signal-cli.1.adoc | 8 +++---- .../asamk/signal/commands/TrustCommand.java | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 98a54756..3cd260b3 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -226,9 +226,9 @@ number:: *-a*, *--trust-all-known-keys*:: Trust all known keys of this user, only use this for testing. -*-v* VERIFIED_FINGERPRINT, *--verified-fingerprint* VERIFIED_FINGERPRINT:: - Specify the safety number or fingerprint of the key, only use this option if you have verified - the fingerprint. +*-v* VERIFIED_SAFETY_NUMBER, *--verified-safety-number* VERIFIED_SAFETY_NUMBER:: + Specify the safety number of the key, only use this option if you have verified + the safety number. updateProfile ~~~~~~~~~~~~~ @@ -355,7 +355,7 @@ Send a message to a group:: signal-cli -u USERNAME send -m "This is a message" -g GROUP_ID Trust new key, after having verified it:: - signal-cli -u USERNAME trust -v FINGER_PRINT NUMBER + signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER Trust new key, without having verified it. Only use this if you don't care about security:: signal-cli -u USERNAME trust -a NUMBER diff --git a/src/main/java/org/asamk/signal/commands/TrustCommand.java b/src/main/java/org/asamk/signal/commands/TrustCommand.java index a507e265..2780dc46 100644 --- a/src/main/java/org/asamk/signal/commands/TrustCommand.java +++ b/src/main/java/org/asamk/signal/commands/TrustCommand.java @@ -23,8 +23,8 @@ public class TrustCommand implements LocalCommand { mutTrust.addArgument("-a", "--trust-all-known-keys") .help("Trust all known keys of this user, only use this for testing.") .action(Arguments.storeTrue()); - mutTrust.addArgument("-v", "--verified-fingerprint") - .help("Specify the fingerprint of the key, only use this option if you have verified the fingerprint."); + mutTrust.addArgument("-v", "--verified-safety-number", "--verified-fingerprint") + .help("Specify the safety number of the key, only use this option if you have verified the safety number."); } @Override @@ -41,13 +41,13 @@ public class TrustCommand implements LocalCommand { return 1; } } else { - String fingerprint = ns.getString("verified_fingerprint"); - if (fingerprint != null) { - fingerprint = fingerprint.replaceAll(" ", ""); - if (fingerprint.length() == 66) { + String safetyNumber = ns.getString("verified_safety_number"); + if (safetyNumber != null) { + safetyNumber = safetyNumber.replaceAll(" ", ""); + if (safetyNumber.length() == 66) { byte[] fingerprintBytes; try { - fingerprintBytes = Hex.toByteArray(fingerprint.toLowerCase(Locale.ROOT)); + fingerprintBytes = Hex.toByteArray(safetyNumber.toLowerCase(Locale.ROOT)); } catch (Exception e) { System.err.println("Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters."); return 1; @@ -63,10 +63,10 @@ public class TrustCommand implements LocalCommand { System.err.println("Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct."); return 1; } - } else if (fingerprint.length() == 60) { + } else if (safetyNumber.length() == 60) { boolean res; try { - res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint); + res = m.trustIdentityVerifiedSafetyNumber(number, safetyNumber); } catch (InvalidNumberException e) { ErrorUtils.handleInvalidNumberException(e); return 1; @@ -76,11 +76,11 @@ public class TrustCommand implements LocalCommand { return 1; } } else { - System.err.println("Fingerprint has invalid format, either specify the old hex fingerprint or the new safety number"); + System.err.println("Safety number has invalid format, either specify the old hex fingerprint or the new safety number"); return 1; } } else { - System.err.println("You need to specify the fingerprint you have verified with -v FINGERPRINT"); + System.err.println("You need to specify the fingerprint/safety number you have verified with -v SAFETY_NUMBER"); return 1; } } -- 2.51.0 From ae41d0c5026fe868c6198e1005344fc78b6e0a2c Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 3 Apr 2020 16:22:10 +0200 Subject: [PATCH 02/16] Output json when receiving messages from dbus and --json parameter is given Fixes #138 --- .../signal/JsonDbusReceiveMessageHandler.java | 25 +++-- src/main/java/org/asamk/signal/JsonError.java | 10 -- .../signal/JsonReceiveMessageHandler.java | 2 + .../asamk/signal/commands/ReceiveCommand.java | 96 ++++++++++++++----- .../signal/{ => json}/JsonAttachment.java | 6 +- .../signal/{ => json}/JsonCallMessage.java | 2 +- .../signal/{ => json}/JsonDataMessage.java | 24 ++++- .../java/org/asamk/signal/json/JsonError.java | 10 ++ .../signal/{ => json}/JsonGroupInfo.java | 6 +- .../{ => json}/JsonMessageEnvelope.java | 23 ++++- .../signal/{ => json}/JsonReceiptMessage.java | 2 +- .../{ => json}/JsonSyncDataMessage.java | 8 +- .../signal/{ => json}/JsonSyncMessage.java | 7 +- .../signal/{ => manager}/JsonStickerPack.java | 4 +- .../org/asamk/signal/manager/Manager.java | 1 - 15 files changed, 176 insertions(+), 50 deletions(-) delete mode 100644 src/main/java/org/asamk/signal/JsonError.java rename src/main/java/org/asamk/signal/{ => json}/JsonAttachment.java (88%) rename src/main/java/org/asamk/signal/{ => json}/JsonCallMessage.java (97%) rename src/main/java/org/asamk/signal/{ => json}/JsonDataMessage.java (60%) create mode 100644 src/main/java/org/asamk/signal/json/JsonError.java rename src/main/java/org/asamk/signal/{ => json}/JsonGroupInfo.java (87%) rename src/main/java/org/asamk/signal/{ => json}/JsonMessageEnvelope.java (71%) rename src/main/java/org/asamk/signal/{ => json}/JsonReceiptMessage.java (95%) rename src/main/java/org/asamk/signal/{ => json}/JsonSyncDataMessage.java (67%) rename src/main/java/org/asamk/signal/{ => json}/JsonSyncMessage.java (89%) rename src/main/java/org/asamk/signal/{ => manager}/JsonStickerPack.java (88%) diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 3ba65f7d..ecb54d07 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -9,9 +9,9 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; -import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import java.util.ArrayList; import java.util.List; @@ -40,12 +40,25 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { e.printStackTrace(); } } else if (content != null) { - if (content.getDataMessage().isPresent()) { + 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.sendSignal(new Signal.ReceiptReceived( + objectPath, + timestamp, + sender + )); + } catch (DBusException e) { + e.printStackTrace(); + } + } + } + } else if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); - if (message.getBody().isPresent()) - System.out.println(message.getBody().get()); - if (!message.isEndSession() && !(message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1Type() != SignalServiceGroup.Type.DELIVER)) { diff --git a/src/main/java/org/asamk/signal/JsonError.java b/src/main/java/org/asamk/signal/JsonError.java deleted file mode 100644 index 5ef2cd7c..00000000 --- a/src/main/java/org/asamk/signal/JsonError.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.asamk.signal; - -class JsonError { - - String message; - - JsonError(Throwable exception) { - this.message = exception.getMessage(); - } -} diff --git a/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java index 1aea2327..5aa57f44 100644 --- a/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.asamk.signal.json.JsonError; +import org.asamk.signal.json.JsonMessageEnvelope; import org.asamk.signal.manager.Manager; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java index 42ab7327..f85aea8b 100644 --- a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -1,5 +1,11 @@ package org.asamk.signal.commands; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; @@ -7,6 +13,7 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; import org.asamk.signal.JsonReceiveMessageHandler; import org.asamk.signal.ReceiveMessageHandler; +import org.asamk.signal.json.JsonMessageEnvelope; import org.asamk.signal.manager.Manager; import org.asamk.signal.util.DateUtils; import org.freedesktop.dbus.DBusConnection; @@ -34,9 +41,27 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { } public int handleCommand(final Namespace ns, final Signal signal, DBusConnection dbusconnection) { - if (dbusconnection != null) { - try { - dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> { + final ObjectMapper jsonProcessor; + if (ns.getBoolean("json")) { + jsonProcessor = new ObjectMapper(); + jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // disable autodetect + jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); + } else { + jsonProcessor = null; + } + try { + dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> { + if (jsonProcessor != null) { + JsonMessageEnvelope envelope = new JsonMessageEnvelope(messageReceived); + ObjectNode result = jsonProcessor.createObjectNode(); + result.putPOJO("envelope", envelope); + try { + jsonProcessor.writeValue(System.out, result); + System.out.println(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n", messageReceived.getSender(), DateUtils.formatTimestamp(messageReceived.getTimestamp()), messageReceived.getMessage())); if (messageReceived.getGroupId().length > 0) { @@ -50,11 +75,39 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { } } System.out.println(); - }); - dbusconnection.addSigHandler(Signal.ReceiptReceived.class, - receiptReceived -> System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n", - receiptReceived.getSender(), DateUtils.formatTimestamp(receiptReceived.getTimestamp())))); - dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> { + } + }); + + dbusconnection.addSigHandler(Signal.ReceiptReceived.class, + receiptReceived -> { + if (jsonProcessor != null) { + JsonMessageEnvelope envelope = new JsonMessageEnvelope(receiptReceived); + ObjectNode result = jsonProcessor.createObjectNode(); + result.putPOJO("envelope", envelope); + try { + jsonProcessor.writeValue(System.out, result); + System.out.println(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n", + receiptReceived.getSender(), DateUtils.formatTimestamp(receiptReceived.getTimestamp()))); + } + }); + + dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> { + if (jsonProcessor != null) { + JsonMessageEnvelope envelope = new JsonMessageEnvelope(syncReceived); + ObjectNode result = jsonProcessor.createObjectNode(); + result.putPOJO("envelope", envelope); + try { + jsonProcessor.writeValue(System.out, result); + System.out.println(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { System.out.print(String.format("Sync Envelope from: %s to: %s\nTimestamp: %s\nBody: %s\n", syncReceived.getSource(), syncReceived.getDestination(), DateUtils.formatTimestamp(syncReceived.getTimestamp()), syncReceived.getMessage())); if (syncReceived.getGroupId().length > 0) { @@ -68,23 +121,22 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { } } System.out.println(); - }); - } catch (UnsatisfiedLinkError e) { - System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); - return 1; - } catch (DBusException e) { - e.printStackTrace(); - return 1; - } - while (true) { - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - return 0; } + }); + } catch (UnsatisfiedLinkError e) { + System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); + return 1; + } catch (DBusException e) { + e.printStackTrace(); + return 1; + } + while (true) { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + return 0; } } - return 0; } @Override diff --git a/src/main/java/org/asamk/signal/JsonAttachment.java b/src/main/java/org/asamk/signal/json/JsonAttachment.java similarity index 88% rename from src/main/java/org/asamk/signal/JsonAttachment.java rename to src/main/java/org/asamk/signal/json/JsonAttachment.java index 58165639..8a405fc4 100644 --- a/src/main/java/org/asamk/signal/JsonAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachment.java @@ -1,4 +1,4 @@ -package org.asamk.signal; +package org.asamk.signal.json; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; @@ -24,4 +24,8 @@ class JsonAttachment { } } } + + JsonAttachment(String filename) { + this.filename = filename; + } } diff --git a/src/main/java/org/asamk/signal/JsonCallMessage.java b/src/main/java/org/asamk/signal/json/JsonCallMessage.java similarity index 97% rename from src/main/java/org/asamk/signal/JsonCallMessage.java rename to src/main/java/org/asamk/signal/json/JsonCallMessage.java index 2c8518f9..c1b1d443 100644 --- a/src/main/java/org/asamk/signal/JsonCallMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonCallMessage.java @@ -1,4 +1,4 @@ -package org.asamk.signal; +package org.asamk.signal.json; import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; import org.whispersystems.signalservice.api.messages.calls.BusyMessage; diff --git a/src/main/java/org/asamk/signal/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java similarity index 60% rename from src/main/java/org/asamk/signal/JsonDataMessage.java rename to src/main/java/org/asamk/signal/json/JsonDataMessage.java index efd8e53e..fc8538aa 100644 --- a/src/main/java/org/asamk/signal/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -1,11 +1,13 @@ -package org.asamk.signal; +package org.asamk.signal.json; +import org.asamk.Signal; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; class JsonDataMessage { @@ -34,4 +36,24 @@ class JsonDataMessage { this.attachments = new ArrayList<>(); } } + + public JsonDataMessage(Signal.MessageReceived messageReceived) { + timestamp = messageReceived.getTimestamp(); + message = messageReceived.getMessage(); + groupInfo = new JsonGroupInfo(messageReceived.getGroupId()); + attachments = messageReceived.getAttachments() + .stream() + .map(JsonAttachment::new) + .collect(Collectors.toList()); + } + + public JsonDataMessage(Signal.SyncMessageReceived messageReceived) { + timestamp = messageReceived.getTimestamp(); + message = messageReceived.getMessage(); + groupInfo = new JsonGroupInfo(messageReceived.getGroupId()); + attachments = messageReceived.getAttachments() + .stream() + .map(JsonAttachment::new) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/asamk/signal/json/JsonError.java b/src/main/java/org/asamk/signal/json/JsonError.java new file mode 100644 index 00000000..29d85c8b --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonError.java @@ -0,0 +1,10 @@ +package org.asamk.signal.json; + +public class JsonError { + + String message; + + public JsonError(Throwable exception) { + this.message = exception.getMessage(); + } +} diff --git a/src/main/java/org/asamk/signal/JsonGroupInfo.java b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java similarity index 87% rename from src/main/java/org/asamk/signal/JsonGroupInfo.java rename to src/main/java/org/asamk/signal/json/JsonGroupInfo.java index 5678b896..572623e4 100644 --- a/src/main/java/org/asamk/signal/JsonGroupInfo.java +++ b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java @@ -1,4 +1,4 @@ -package org.asamk.signal; +package org.asamk.signal.json; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -27,4 +27,8 @@ class JsonGroupInfo { } this.type = groupInfo.getType().toString(); } + + JsonGroupInfo(byte[] groupId) { + this.groupId = Base64.encodeBytes(groupId); + } } diff --git a/src/main/java/org/asamk/signal/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java similarity index 71% rename from src/main/java/org/asamk/signal/JsonMessageEnvelope.java rename to src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index 9a275970..3279d941 100644 --- a/src/main/java/org/asamk/signal/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -1,10 +1,11 @@ -package org.asamk.signal; +package org.asamk.signal.json; +import org.asamk.Signal; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -class JsonMessageEnvelope { +public class JsonMessageEnvelope { String source; int sourceDevice; @@ -44,4 +45,22 @@ class JsonMessageEnvelope { } } } + + public JsonMessageEnvelope(Signal.MessageReceived messageReceived) { + source = messageReceived.getSender(); + timestamp = messageReceived.getTimestamp(); + dataMessage = new JsonDataMessage(messageReceived); + } + + public JsonMessageEnvelope(Signal.ReceiptReceived receiptReceived) { + source = receiptReceived.getSender(); + timestamp = receiptReceived.getTimestamp(); + isReceipt = true; + } + + public JsonMessageEnvelope(Signal.SyncMessageReceived messageReceived) { + source = messageReceived.getSource(); + timestamp = messageReceived.getTimestamp(); + syncMessage = new JsonSyncMessage(messageReceived); + } } diff --git a/src/main/java/org/asamk/signal/JsonReceiptMessage.java b/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java similarity index 95% rename from src/main/java/org/asamk/signal/JsonReceiptMessage.java rename to src/main/java/org/asamk/signal/json/JsonReceiptMessage.java index fd875af5..1b896053 100644 --- a/src/main/java/org/asamk/signal/JsonReceiptMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java @@ -1,4 +1,4 @@ -package org.asamk.signal; +package org.asamk.signal.json; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; diff --git a/src/main/java/org/asamk/signal/JsonSyncDataMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java similarity index 67% rename from src/main/java/org/asamk/signal/JsonSyncDataMessage.java rename to src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java index b72fb26d..d253b197 100644 --- a/src/main/java/org/asamk/signal/JsonSyncDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java @@ -1,5 +1,6 @@ -package org.asamk.signal; +package org.asamk.signal.json; +import org.asamk.Signal; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; class JsonSyncDataMessage extends JsonDataMessage { @@ -12,4 +13,9 @@ class JsonSyncDataMessage extends JsonDataMessage { this.destination = transcriptMessage.getDestination().get().getNumber().get(); } } + + JsonSyncDataMessage(Signal.SyncMessageReceived messageReceived) { + super(messageReceived); + destination = messageReceived.getDestination(); + } } diff --git a/src/main/java/org/asamk/signal/JsonSyncMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java similarity index 89% rename from src/main/java/org/asamk/signal/JsonSyncMessage.java rename to src/main/java/org/asamk/signal/json/JsonSyncMessage.java index 326ec4ed..27766bda 100644 --- a/src/main/java/org/asamk/signal/JsonSyncMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java @@ -1,5 +1,6 @@ -package org.asamk.signal; +package org.asamk.signal.json; +import org.asamk.Signal; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -42,4 +43,8 @@ class JsonSyncMessage { this.type = JsonSyncMessageType.REQUEST_SYNC; } } + + JsonSyncMessage(Signal.SyncMessageReceived messageReceived) { + sentMessage = new JsonSyncDataMessage(messageReceived); + } } diff --git a/src/main/java/org/asamk/signal/JsonStickerPack.java b/src/main/java/org/asamk/signal/manager/JsonStickerPack.java similarity index 88% rename from src/main/java/org/asamk/signal/JsonStickerPack.java rename to src/main/java/org/asamk/signal/manager/JsonStickerPack.java index 4594c5d1..a7e5eb7f 100644 --- a/src/main/java/org/asamk/signal/JsonStickerPack.java +++ b/src/main/java/org/asamk/signal/manager/JsonStickerPack.java @@ -1,10 +1,10 @@ -package org.asamk.signal; +package org.asamk.signal.manager; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; -public class JsonStickerPack { +class JsonStickerPack { @JsonProperty public String title; diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 06350982..11c49d84 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.Signal; import org.asamk.signal.AttachmentInvalidException; import org.asamk.signal.GroupNotFoundException; -import org.asamk.signal.JsonStickerPack; import org.asamk.signal.NotAGroupMemberException; import org.asamk.signal.StickerPackInvalidException; import org.asamk.signal.TrustLevel; -- 2.51.0 From 6ca695b65e72daf06e6079b774f7b6f6b1e25990 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 3 Apr 2020 18:00:27 +0200 Subject: [PATCH 03/16] Bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e6cad8c8..157e8601 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ targetCompatibility = JavaVersion.VERSION_1_8 mainClassName = 'org.asamk.signal.Main' -version = '0.6.6' +version = '0.6.7' compileJava.options.encoding = 'UTF-8' -- 2.51.0 From 19b01ff2e9cd45335e59f064acb3731478d0b16d Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 21 Apr 2020 20:33:23 +0200 Subject: [PATCH 04/16] Also catch IllegalArgumentException when sending messages during receive It's necessary to keep receiving messages if a session state is corrupted. e.g: Exception in thread "main" java.lang.IllegalArgumentException: Empty key at java.base/javax.crypto.spec.SecretKeySpec.(Unknown Source) at org.whispersystems.libsignal.ratchet.ChainKey.getBaseMaterial(ChainKey.java:57) at org.whispersystems.libsignal.ratchet.ChainKey.getMessageKeys(ChainKey.java:47) at org.whispersystems.libsignal.SessionCipher.encrypt(SessionCipher.java:97) at org.signal.libsignal.metadata.SealedSessionCipher.encrypt(SealedSessionCipher.java:70) at org.whispersystems.signalservice.api.crypto.SignalServiceCipher.encrypt(SignalServiceCipher.java:86) at org.whispersystems.signalservice.api.SignalServiceMessageSender.getEncryptedMessage(SignalServiceMessageSender.java:1456) at org.whispersystems.signalservice.api.SignalServiceMessageSender.getEncryptedMessages(SignalServiceMessageSender.java:1406) at org.whispersystems.signalservice.api.SignalServiceMessageSender.sendMessage(SignalServiceMessageSender.java:1276) at org.whispersystems.signalservice.api.SignalServiceMessageSender.sendReceipt(SignalServiceMessageSender.java:206) at org.asamk.signal.manager.Manager.sendReceipt(Manager.java:686) at org.asamk.signal.manager.Manager.handleMessage(Manager.java:1562) at org.asamk.signal.manager.Manager.receiveMessages(Manager.java:1496) at org.asamk.signal.commands.ReceiveCommand.handleCommand(ReceiveCommand.java:160) at org.asamk.signal.Main.handleCommands(Main.java:137) at org.asamk.signal.Main.main(Main.java:60) --- src/main/java/org/asamk/signal/manager/Manager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 11c49d84..cec04431 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1560,7 +1560,7 @@ public class Manager implements Signal { if (content.isNeedsReceipt()) { try { sendReceipt(sender, message.getTimestamp()); - } catch (IOException | UntrustedIdentityException e) { + } catch (IOException | UntrustedIdentityException | IllegalArgumentException e) { e.printStackTrace(); } } @@ -1579,21 +1579,21 @@ public class Manager implements Signal { if (rm.isContactsRequest()) { try { sendContacts(); - } catch (UntrustedIdentityException | IOException e) { + } catch (UntrustedIdentityException | IOException | IllegalArgumentException e) { e.printStackTrace(); } } if (rm.isGroupsRequest()) { try { sendGroups(); - } catch (UntrustedIdentityException | IOException e) { + } catch (UntrustedIdentityException | IOException | IllegalArgumentException e) { e.printStackTrace(); } } if (rm.isBlockedListRequest()) { try { sendBlockedList(); - } catch (UntrustedIdentityException | IOException e) { + } catch (UntrustedIdentityException | IOException | IllegalArgumentException e) { e.printStackTrace(); } } -- 2.51.0 From 08749fcee090f0b36f5899e1207a3333798c8a59 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 21 Apr 2020 20:36:28 +0200 Subject: [PATCH 05/16] When sending an end session message clear local session store also if sending message fails --- src/main/java/org/asamk/signal/manager/Manager.java | 10 +++++++++- 1 file changed, 9 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 cec04431..b6964917 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -732,7 +732,15 @@ public class Manager implements Signal { SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asEndSessionMessage(); - sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); + final Collection signalServiceAddresses = getSignalServiceAddresses(recipients); + try { + sendMessageLegacy(messageBuilder, signalServiceAddresses); + } catch (Exception e) { + for (SignalServiceAddress address : signalServiceAddresses) { + handleEndSession(address); + } + throw e; + } } @Override -- 2.51.0 From 207075c236104dd746a15c76c3a45066df0e4e93 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 6 May 2020 08:37:30 +0200 Subject: [PATCH 06/16] Update README.md Closes #286 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4bed3b8..31a12c94 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Important: The USERNAME (your phone number) must include the country calling cod You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly to the voice call verification by adding the --voice switch at the end of above register command. -* Verify the number using the code received via SMS or voice +* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code to your account signal-cli -u USERNAME verify CODE -- 2.51.0 From 00777a469c7ec152555a2e92eaf13b8dd0bf43f0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 6 May 2020 09:13:49 +0200 Subject: [PATCH 07/16] Switch to hypfvieh dbus-java Removes transitive dependency on libmatthew-unix-java Fixes #285 --- build.gradle | 6 ++---- src/main/java/org/asamk/Signal.java | 5 +++-- .../org/asamk/signal/DbusReceiveMessageHandler.java | 2 +- .../asamk/signal/JsonDbusReceiveMessageHandler.java | 10 +++++----- src/main/java/org/asamk/signal/Main.java | 8 ++++---- .../java/org/asamk/signal/commands/DaemonCommand.java | 8 ++++---- .../org/asamk/signal/commands/ExtendedDbusCommand.java | 2 +- .../java/org/asamk/signal/commands/ReceiveCommand.java | 2 +- src/main/java/org/asamk/signal/manager/Manager.java | 6 ++++++ 9 files changed, 27 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 157e8601..0fc3d1f9 100644 --- a/build.gradle +++ b/build.gradle @@ -13,9 +13,6 @@ compileJava.options.encoding = 'UTF-8' repositories { mavenLocal() - maven { - url "https://raw.github.com/AsamK/maven/master/releases/" - } mavenCentral() } @@ -23,7 +20,8 @@ dependencies { compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_7' compile 'org.bouncycastle:bcprov-jdk15on:1.64' compile 'net.sourceforge.argparse4j:argparse4j:0.8.1' - compile 'org.freedesktop.dbus:dbus-java:2.7.0' + compile 'com.github.hypfvieh:dbus-java:3.2.0' + compile 'org.slf4j:slf4j-nop:1.7.30' } jar { diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index f0e01dd8..654f365f 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -2,9 +2,9 @@ package org.asamk; import org.asamk.signal.AttachmentInvalidException; import org.asamk.signal.GroupNotFoundException; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSignal; import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.messages.DBusSignal; import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -98,6 +98,7 @@ public interface Signal extends DBusInterface { } class SyncMessageReceived extends DBusSignal { + private long timestamp; private String source; private String destination; diff --git a/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java index cebabc18..8fb11a59 100644 --- a/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java @@ -1,7 +1,7 @@ package org.asamk.signal; import org.asamk.signal.manager.Manager; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnection; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index ecb54d07..0c5775db 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -2,7 +2,7 @@ package org.asamk.signal; import org.asamk.Signal; import org.asamk.signal.manager.Manager; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceContent; @@ -31,7 +31,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { static void sendReceivedMessageToDbus(SignalServiceEnvelope envelope, SignalServiceContent content, DBusConnection conn, final String objectPath, Manager m) { if (envelope.isReceipt()) { try { - conn.sendSignal(new Signal.ReceiptReceived( + conn.sendMessage(new Signal.ReceiptReceived( objectPath, envelope.getTimestamp(), !envelope.isUnidentifiedSender() && envelope.hasSource() ? envelope.getSourceE164().get() : content.getSender().getNumber().get() @@ -46,7 +46,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { final String sender = !envelope.isUnidentifiedSender() && envelope.hasSource() ? envelope.getSourceE164().get() : content.getSender().getNumber().get(); for (long timestamp : receiptMessage.getTimestamps()) { try { - conn.sendSignal(new Signal.ReceiptReceived( + conn.sendMessage(new Signal.ReceiptReceived( objectPath, timestamp, sender @@ -63,7 +63,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { !(message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1Type() != SignalServiceGroup.Type.DELIVER)) { try { - conn.sendSignal(new Signal.MessageReceived( + conn.sendMessage(new Signal.MessageReceived( objectPath, message.getTimestamp(), envelope.isUnidentifiedSender() || !envelope.hasSource() ? content.getSender().getNumber().get() : envelope.getSourceE164().get(), @@ -84,7 +84,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { SignalServiceDataMessage message = transcript.getMessage(); try { - conn.sendSignal(new Signal.SyncMessageReceived( + conn.sendMessage(new Signal.SyncMessageReceived( objectPath, transcript.getTimestamp(), envelope.getSourceAddress().getNumber().get(), diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 5e37bf3b..38c7a68d 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -36,7 +36,7 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.SecurityProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -76,11 +76,11 @@ public class Main { if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { try { m = null; - int busType; + DBusConnection.DBusBusType busType; if (ns.getBoolean("dbus_system")) { - busType = DBusConnection.SYSTEM; + busType = DBusConnection.DBusBusType.SYSTEM; } else { - busType = DBusConnection.SESSION; + busType = DBusConnection.DBusBusType.SESSION; } dBusConn = DBusConnection.getConnection(busType); ts = dBusConn.getRemoteObject( diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 85fee723..11805b44 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -7,7 +7,7 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.DbusReceiveMessageHandler; import org.asamk.signal.JsonDbusReceiveMessageHandler; import org.asamk.signal.manager.Manager; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import java.io.IOException; @@ -41,11 +41,11 @@ public class DaemonCommand implements LocalCommand { DBusConnection conn = null; try { try { - int busType; + DBusConnection.DBusBusType busType; if (ns.getBoolean("system")) { - busType = DBusConnection.SYSTEM; + busType = DBusConnection.DBusBusType.SYSTEM; } else { - busType = DBusConnection.SESSION; + busType = DBusConnection.DBusBusType.SESSION; } conn = DBusConnection.getConnection(busType); conn.exportObject(SIGNAL_OBJECTPATH, m); diff --git a/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java b/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java index b7f70dee..f9cd9de8 100644 --- a/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java +++ b/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java @@ -3,7 +3,7 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import org.asamk.Signal; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnection; public interface ExtendedDbusCommand extends Command { diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java index f85aea8b..bc3acbde 100644 --- a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -16,7 +16,7 @@ import org.asamk.signal.ReceiveMessageHandler; import org.asamk.signal.json.JsonMessageEnvelope; import org.asamk.signal.manager.Manager; import org.asamk.signal.util.DateUtils; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.whispersystems.util.Base64; diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index b6964917..ad770617 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.Signal; import org.asamk.signal.AttachmentInvalidException; +import org.asamk.signal.DbusConfig; import org.asamk.signal.GroupNotFoundException; import org.asamk.signal.NotAGroupMemberException; import org.asamk.signal.StickerPackInvalidException; @@ -1825,6 +1826,11 @@ public class Manager implements Signal { return false; } + @Override + public String getObjectPath() { + return null; + } + private void sendGroups() throws IOException, UntrustedIdentityException { File groupsFile = IOUtils.createTempFile(); -- 2.51.0 From d8ef312b5f94bf297808dff199a1116746910201 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 6 May 2020 09:18:48 +0200 Subject: [PATCH 08/16] Remove version check which isn't working correctly --- build.gradle | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/build.gradle b/build.gradle index 0fc3d1f9..c6cf9697 100644 --- a/build.gradle +++ b/build.gradle @@ -41,41 +41,3 @@ run { args Eval.me(appArgs) } } - -// Find any 3rd party libraries which have released new versions -// to the central Maven repo since we last upgraded. -// http://daniel.gredler.net/2011/08/08/gradle-keeping-libraries-up-to-date/ -task checkLibVersions { - doLast { - def checked = [:] - allprojects { - configurations.each { configuration -> - configuration.allDependencies.each { dependency -> - def version = dependency.version - if (!version.contains('SNAPSHOT') && !checked[dependency]) { - def group = dependency.group - def path = group.replace('.', '/') - def name = dependency.name - def url = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" - try { - def metadata = new XmlSlurper().parseText(url.toURL().text) - def versions = metadata.versioning.versions.version.collect { it.text() } - versions.removeAll { it.toLowerCase().contains('alpha') } - versions.removeAll { it.toLowerCase().contains('beta') } - versions.removeAll { it.toLowerCase().contains('rc') } - def newest = versions.max() - if (version != newest) { - println "$group:$name $version -> $newest" - } - } catch (FileNotFoundException e) { - logger.debug "Unable to download $url: $e.message" - } catch (org.xml.sax.SAXParseException e) { - logger.debug "Unable to parse $url: $e.message" - } - checked[dependency] = true - } - } - } - } - } -} -- 2.51.0 From a486b752e81eacc607db434dc36234a28983a4c3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 6 May 2020 09:24:54 +0200 Subject: [PATCH 09/16] Improve asciidoc formatting of the man page --- man/signal-cli.1.adoc | 338 ++++++++++++++++++++---------------------- 1 file changed, 162 insertions(+), 176 deletions(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 3cd260b3..ecd7ac9f 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -5,276 +5,268 @@ vim:set ts=4 sw=4 tw=82 noet: = signal-cli (1) -Name ----- +== Name + signal-cli - A commandline and dbus interface for the Signal messenger -Synopsis --------- +== Synopsis + *signal-cli* [--config CONFIG] [-h | -v | -u USERNAME | --dbus | --dbus-system] command [command-options] -Description ------------ +== Description -signal-cli is a commandline interface for libsignal-service-java. It supports -registering, verifying, sending and receiving messages. For registering you need a -phone number where you can receive SMS or incoming calls. -signal-cli was primarily developed to be used on servers to notify admins of -important events. For this use-case, it has a dbus interface, that can be used to -send messages from any programming language that has dbus bindings. +signal-cli is a commandline interface for libsignal-service-java. +It supports registering, verifying, sending and receiving messages. +For registering you need a phone number where you can receive SMS or incoming calls. +signal-cli was primarily developed to be used on servers to notify admins of important events. +For this use-case, it has a dbus interface, that can be used to send messages from any programming language that has dbus bindings. -Options -------- +== Options *-h*, *--help*:: - Show help message and quit. +Show help message and quit. *-v*, *--version*:: - Print the version and quit. +Print the version and quit. *--config* CONFIG:: - Set the path, where to store the config. - Make sure you have full read/write access to the given directory. - (Default: `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`)) +Set the path, where to store the config. +Make sure you have full read/write access to the given directory. +(Default: `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`)) *-u* USERNAME, *--username* USERNAME:: - Specify your phone number, that will be your identifier. - The phone number must include the country calling code, i.e. the number must - start with a "+" sign. +Specify your phone number, that will be your identifier. +The phone number must include the country calling code, i.e. the number must start with a "+" sign. *--dbus*:: - Make request via user dbus. +Make request via user dbus. *--dbus-system*:: - Make request via system dbus. +Make request via system dbus. -Commands --------- +== Commands -register -~~~~~~~~ -Register a phone number with SMS or voice verification. Use the verify command to -complete the verification. +=== register + +Register a phone number with SMS or voice verification. +Use the verify command to complete the verification. *-v*, *--voice*:: - The verification should be done over voice, not SMS. +The verification should be done over voice, not SMS. + +=== verify -verify -~~~~~~ Verify the number using the code received via SMS or voice. VERIFICATIONCODE:: - The verification code. +The verification code. *-p* PIN, *--pin* PIN:: - The registration lock PIN, that was set by the user. Only required if a PIN was set. +The registration lock PIN, that was set by the user. +Only required if a PIN was set. + +=== unregister -unregister -~~~~~~~~~~ Disable push support for this device, i.e. this device won't receive any more messages. If this is the master device, other users can't send messages to this number anymore. Use "updateAccount" to undo this. To remove a linked device, use "removeDevice" from the master device. -updateAccount -~~~~~~~~~~~~~ +=== updateAccount + Update the account attributes on the signal server. Can fix problems with receiving messages. -setPin -~~~~~~ +=== setPin + Set a registration lock pin, to prevent others from registering this number. REGISTRATION_LOCK_PIN:: - The registration lock PIN, that will be required for new registrations (resets after 7 days of inactivity) +The registration lock PIN, that will be required for new registrations (resets after 7 days of inactivity) + +=== removePin -removePin -~~~~~~~~~ Remove the registration lock pin. -link -~~~~ -Link to an existing device, instead of registering a new number. This shows a -"tsdevice:/…" URI. If you want to connect to another signal-cli instance, you can -just use this URI. If you want to link to an Android/iOS device, create a QR code -with the URI (e.g. with qrencode) and scan that in the Signal app. +=== link + +Link to an existing device, instead of registering a new number. +This shows a "tsdevice:/…" URI. If you want to connect to another signal-cli instance, you can just use this URI. If you want to link to an Android/iOS device, create a QR code with the URI (e.g. with qrencode) and scan that in the Signal app. *-n* NAME, *--name* NAME:: - Optionally specify a name to describe this new device. By default "cli" will - be used. +Optionally specify a name to describe this new device. +By default "cli" will be used. + +=== addDevice -addDevice -~~~~~~~~~ -Link another device to this device. Only works, if this is the master device. +Link another device to this device. +Only works, if this is the master device. *--uri* URI:: - Specify the uri contained in the QR code shown by the new device. +Specify the uri contained in the QR code shown by the new device. + +=== listDevices -listDevices -~~~~~~~~~~~ Show a list of connected devices. -removeDevice -~~~~~~~~~~~~ -Remove a connected device. Only works, if this is the master device. +=== removeDevice + +Remove a connected device. +Only works, if this is the master device. *-d* DEVICEID, *--deviceId* DEVICEID:: - Specify the device you want to remove. Use listDevices to see the deviceIds. +Specify the device you want to remove. +Use listDevices to see the deviceIds. + +=== send -send -~~~~ Send a message to another user or group. RECIPIENT:: - Specify the recipients’ phone number. +Specify the recipients’ phone number. *-g* GROUP, *--group* GROUP:: - Specify the recipient group ID in base64 encoding. +Specify the recipient group ID in base64 encoding. *-m* MESSAGE, *--message* MESSAGE:: - Specify the message, if missing, standard input is used. +Specify the message, if missing, standard input is used. *-a* [ATTACHMENT [ATTACHMENT ...]], *--attachment* [ATTACHMENT [ATTACHMENT ...]]:: - Add one or more files as attachment. +Add one or more files as attachment. *-e*, *--endsession*:: - Clear session state and send end session message. +Clear session state and send end session message. + +=== sendReaction -sendReaction -~~~~~~~~~~~~ Send reaction to a previously received or sent message. RECIPIENT:: - Specify the recipients’ phone number. +Specify the recipients’ phone number. *-g* GROUP, *--group* GROUP:: - Specify the recipient group ID in base64 encoding. +Specify the recipient group ID in base64 encoding. *-e* EMOJI, *--emoji* EMOJI:: - Specify the emoji, should be a single unicode grapheme cluster. +Specify the emoji, should be a single unicode grapheme cluster. *-a* NUMBER, *--target-author* NUMBER:: - Specify the number of the author of the message to which to react. +Specify the number of the author of the message to which to react. *-t* TIMESTAMP, *--target-timestamp* TIMESTAMP:: - Specify the timestamp of the message to which to react. +Specify the timestamp of the message to which to react. *-r*, *--remove*:: - Remove a reaction. +Remove a reaction. -receive -~~~~~~~ -Query the server for new messages. New messages are printed on standardoutput and -attachments are downloaded to the config directory. +=== receive + +Query the server for new messages. +New messages are printed on standardoutput and attachments are downloaded to the config directory. *-t* TIMEOUT, *--timeout* TIMEOUT:: - Number of seconds to wait for new messages (negative values disable timeout). - Default is 5 seconds. +Number of seconds to wait for new messages (negative values disable timeout). +Default is 5 seconds. *--ignore-attachments*:: - Don’t download attachments of received messages. +Don’t download attachments of received messages. *--json*:: - Output received messages in json format, one object per line. +Output received messages in json format, one object per line. + +=== updateGroup -updateGroup -~~~~~~~~~~~ Create or update a group. *-g* GROUP, *--group* GROUP:: - Specify the recipient group ID in base64 encoding. If not specified, a new - group with a new random ID is generated. +Specify the recipient group ID in base64 encoding. +If not specified, a new group with a new random ID is generated. *-n* NAME, *--name* NAME:: - Specify the new group name. +Specify the new group name. *-a* AVATAR, *--avatar* AVATAR:: - Specify a new group avatar image file. +Specify a new group avatar image file. *-m* [MEMBER [MEMBER ...]], *--member* [MEMBER [MEMBER ...]]:: - Specify one or more members to add to the group. +Specify one or more members to add to the group. + +=== quitGroup -quitGroup -~~~~~~~~~ Send a quit group message to all group members and remove self from member list. *-g* GROUP, *--group* GROUP:: - Specify the recipient group ID in base64 encoding. +Specify the recipient group ID in base64 encoding. + +=== listGroups -listGroups -~~~~~~~~~~~ Show a list of known groups. *-d*, *--detailed*:: - Include the list of members of each group. +Include the list of members of each group. -listIdentities -~~~~~~~~~~~~~~ -List all known identity keys and their trust status, fingerprint and safety -number. +=== listIdentities + +List all known identity keys and their trust status, fingerprint and safety number. *-n* NUMBER, *--number* NUMBER:: - Only show identity keys for the given phone number. +Only show identity keys for the given phone number. + +=== trust -trust -~~~~~ -Set the trust level of a given number. The first time a key for a number is seen, -it is trusted by default (TOFU). If the key changes, the new key must be trusted -manually. +Set the trust level of a given number. +The first time a key for a number is seen, it is trusted by default (TOFU). +If the key changes, the new key must be trusted manually. number:: - Specify the phone number, for which to set the trust. +Specify the phone number, for which to set the trust. *-a*, *--trust-all-known-keys*:: - Trust all known keys of this user, only use this for testing. +Trust all known keys of this user, only use this for testing. *-v* VERIFIED_SAFETY_NUMBER, *--verified-safety-number* VERIFIED_SAFETY_NUMBER:: - Specify the safety number of the key, only use this option if you have verified - the safety number. +Specify the safety number of the key, only use this option if you have verified the safety number. + +=== updateProfile -updateProfile -~~~~~~~~~~~~~ Update the name and/or avatar image visible by message recipients for the current users. -The profile is stored encrypted on the Signal servers. The decryption key is sent -with every outgoing messages (excluding group messages). +The profile is stored encrypted on the Signal servers. +The decryption key is sent with every outgoing messages (excluding group messages). *--name*:: - New name visible by message recipients. +New name visible by message recipients. *--avatar*:: - Path to the new avatar visible by message recipients. +Path to the new avatar visible by message recipients. *--remove-avatar*:: - Remove the avatar visible by message recipients. +Remove the avatar visible by message recipients. + +=== updateContact -updateContact -~~~~~~~~~~~~~ -Update the info associated to a number on our contact list. This change is only -local but can be synchronized to other devices by using `sendContacts` (see -below). +Update the info associated to a number on our contact list. +This change is only local but can be synchronized to other devices by using `sendContacts` (see below). If the contact doesn't exist yet, it will be added. NUMBER:: - Specify the contact phone number. +Specify the contact phone number. *-n*, *--name*:: - Specify the new name for this contact. +Specify the new name for this contact. -block -~~~~~ -Block the given contacts or groups (no messages will be received). This change is only -local but can be synchronized to other devices by using `sendContacts` (see -below). +=== block + +Block the given contacts or groups (no messages will be received). +This change is only local but can be synchronized to other devices by using `sendContacts` (see below). [CONTACT [CONTACT ...]]:: - Specify the phone numbers of contacts that should be blocked. +Specify the phone numbers of contacts that should be blocked. *-g* [GROUP [GROUP ...]], *--group* [GROUP [GROUP ...]]:: - Specify the group IDs that should be blocked in base64 encoding. +Specify the group IDs that should be blocked in base64 encoding. + +=== unblock -unblock -~~~~~~~ -Unblock the given contacts or groups (messages will be received again). This change is only -local but can be synchronized to other devices by using `sendContacts` (see -below). +Unblock the given contacts or groups (messages will be received again). +This change is only local but can be synchronized to other devices by using `sendContacts` (see below). [CONTACT [CONTACT ...]]:: Specify the phone numbers of contacts that should be unblocked. @@ -282,18 +274,18 @@ Specify the phone numbers of contacts that should be unblocked. *-g* [GROUP [GROUP ...]], *--group* [GROUP [GROUP ...]]:: Specify the group IDs that should be unblocked in base64 encoding. -sendContacts -~~~~~~~~~~~~ +=== sendContacts + Send a synchronization message with the local contacts list to all linked devices. This command should only be used if this is the master device. -uploadStickerPack -~~~~~~~~~~~~~~~~~ -Upload a new sticker pack, consisting of a manifest file and the stickers in WebP -format (maximum size for a sticker file is 100KiB). +=== uploadStickerPack + +Upload a new sticker pack, consisting of a manifest file and the stickers in WebP format (maximum size for a sticker file is 100KiB). The required manifest.json has the following format: -```json +[source,json] +---- { "title": "", "author": "", @@ -309,61 +301,57 @@ The required manifest.json has the following format: ... ] } -``` +---- PATH:: - The path of the manifest.json or a zip file containing the sticker pack you - wish to upload. +The path of the manifest.json or a zip file containing the sticker pack you wish to upload. -daemon -~~~~~~ -signal-cli can run in daemon mode and provides an experimental dbus interface. For -dbus support you need jni/unix-java.so installed on your system (Debian: +=== daemon + +signal-cli can run in daemon mode and provides an experimental dbus interface. +For dbus support you need jni/unix-java.so installed on your system (Debian: libunixsocket-java ArchLinux: libmatthew-unix-java (AUR)). *--system*:: - Use DBus system bus instead of user bus. +Use DBus system bus instead of user bus. *--ignore-attachments*:: - Don’t download attachments of received messages. - +Don’t download attachments of received messages. -Examples --------- +== Examples Register a number (with SMS verification):: - signal-cli -u USERNAME register +signal-cli -u USERNAME register Verify the number using the code received via SMS or voice:: - signal-cli -u USERNAME verify CODE +signal-cli -u USERNAME verify CODE Send a message to one or more recipients:: - signal-cli -u USERNAME send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]] +signal-cli -u USERNAME send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]] Pipe the message content from another process:: - uname -a | signal-cli -u USERNAME send [RECIPIENT [RECIPIENT ...]] +uname -a | signal-cli -u USERNAME send [RECIPIENT [RECIPIENT ...]] Create a group:: - signal-cli -u USERNAME updateGroup -n "Group name" -m [MEMBER [MEMBER ...]] +signal-cli -u USERNAME updateGroup -n "Group name" -m [MEMBER [MEMBER ...]] Add member to a group:: - signal-cli -u USERNAME updateGroup -g GROUP_ID -m "NEW_MEMBER" +signal-cli -u USERNAME updateGroup -g GROUP_ID -m "NEW_MEMBER" Leave a group:: - signal-cli -u USERNAME quitGroup -g GROUP_ID +signal-cli -u USERNAME quitGroup -g GROUP_ID Send a message to a group:: - signal-cli -u USERNAME send -m "This is a message" -g GROUP_ID +signal-cli -u USERNAME send -m "This is a message" -g GROUP_ID Trust new key, after having verified it:: - signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER +signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER Trust new key, without having verified it. Only use this if you don't care about security:: - signal-cli -u USERNAME trust -a NUMBER +signal-cli -u USERNAME trust -a NUMBER -Files ------ -The password and cryptographic keys are created when registering and stored in the -current users home directory, the directory can be changed with *--config*: +== Files + +The password and cryptographic keys are created when registering and stored in the current users home directory, the directory can be changed with *--config*: `$XDG_DATA_HOME/signal-cli/` (`$HOME/.local/share/signal-cli/`) @@ -373,10 +361,8 @@ For legacy users, the old config directories are used as a fallback: $HOME/.config/textsecure/ +== Authors -Authors -------- - -Maintained by AsamK , who is assisted by other open -source contributors. For more information about signal-cli development, see +Maintained by AsamK , who is assisted by other open source contributors. +For more information about signal-cli development, see . -- 2.51.0 From 26840a2f0fb1042483fe58d6161948865881dee6 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 6 May 2020 20:11:26 +0200 Subject: [PATCH 10/16] Update dependencies --- build.gradle | 2 +- .../signal/JsonDbusReceiveMessageHandler.java | 2 +- .../asamk/signal/ReceiveMessageHandler.java | 4 +- .../org/asamk/signal/json/JsonAttachment.java | 2 +- .../org/asamk/signal/manager/BaseConfig.java | 19 ++++++- .../org/asamk/signal/manager/Manager.java | 55 ++++++++++++------- .../java/org/asamk/signal/manager/Utils.java | 5 +- 7 files changed, 60 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index c6cf9697..90289456 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ repositories { } dependencies { - compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_7' + compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_8' compile 'org.bouncycastle:bcprov-jdk15on:1.64' compile 'net.sourceforge.argparse4j:argparse4j:0.8.1' compile 'com.github.hypfvieh:dbus-java:3.2.0' diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 0c5775db..0728b871 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -107,7 +107,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { if (message.getAttachments().isPresent()) { for (SignalServiceAttachment attachment : message.getAttachments().get()) { if (attachment.isPointer()) { - attachments.add(m.getAttachmentFile(attachment.asPointer().getId()).getAbsolutePath()); + attachments.add(m.getAttachmentFile(attachment.asPointer().getRemoteId()).getAbsolutePath()); } } } diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index fe3dd669..898f382c 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -358,12 +358,12 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")"); if (attachment.isPointer()) { final SignalServiceAttachmentPointer pointer = attachment.asPointer(); - System.out.println(" Id: " + pointer.getId() + " Key length: " + pointer.getKey().length); + System.out.println(" Id: " + pointer.getRemoteId() + " Key length: " + pointer.getKey().length); System.out.println(" Filename: " + (pointer.getFileName().isPresent() ? pointer.getFileName().get() : "-")); System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : "")); System.out.println(" Voice note: " + (pointer.getVoiceNote() ? "yes" : "no")); System.out.println(" Dimensions: " + pointer.getWidth() + "x" + pointer.getHeight()); - File file = m.getAttachmentFile(pointer.getId()); + File file = m.getAttachmentFile(pointer.getRemoteId()); if (file.exists()) { System.out.println(" Stored plaintext in: " + file); } diff --git a/src/main/java/org/asamk/signal/json/JsonAttachment.java b/src/main/java/org/asamk/signal/json/JsonAttachment.java index 8a405fc4..1949171a 100644 --- a/src/main/java/org/asamk/signal/json/JsonAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachment.java @@ -15,7 +15,7 @@ class JsonAttachment { final SignalServiceAttachmentPointer pointer = attachment.asPointer(); if (attachment.isPointer()) { - this.id = String.valueOf(pointer.getId()); + this.id = String.valueOf(pointer.getRemoteId()); if (pointer.getFileName().isPresent()) { this.filename = pointer.getFileName().get(); } diff --git a/src/main/java/org/asamk/signal/manager/BaseConfig.java b/src/main/java/org/asamk/signal/manager/BaseConfig.java index 1461d99e..ade0288d 100644 --- a/src/main/java/org/asamk/signal/manager/BaseConfig.java +++ b/src/main/java/org/asamk/signal/manager/BaseConfig.java @@ -1,5 +1,6 @@ package org.asamk.signal.manager; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; @@ -10,8 +11,11 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl; import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import okhttp3.Dns; import okhttp3.Interceptor; public class BaseConfig { @@ -27,10 +31,13 @@ public class BaseConfig { private final static String URL = "https://textsecure-service.whispersystems.org"; private final static String CDN_URL = "https://cdn.signal.org"; + private final static String CDN2_URL = "https://cdn2.signal.org"; private final static String SIGNAL_KEY_BACKUP_URL = "https://api.backup.signal.org"; private final static String STORAGE_URL = "https://storage.signal.org"; private final static TrustStore TRUST_STORE = new WhisperTrustStore(); + private final static Optional dns = Optional.absent(); + private final static Interceptor userAgentInterceptor = chain -> chain.proceed(chain.request().newBuilder() .header("User-Agent", USER_AGENT) @@ -42,16 +49,24 @@ public class BaseConfig { final static SignalServiceConfiguration serviceConfiguration = new SignalServiceConfiguration( new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, - new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, new SignalCdnUrl[]{new SignalCdnUrl(CDN2_URL, TRUST_STORE)}), new SignalContactDiscoveryUrl[0], new SignalKeyBackupServiceUrl[]{new SignalKeyBackupServiceUrl(SIGNAL_KEY_BACKUP_URL, TRUST_STORE)}, new SignalStorageUrl[]{new SignalStorageUrl(STORAGE_URL, TRUST_STORE)}, interceptors, + dns, zkGroupServerPublicParams ); - static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false); + static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false, false); private BaseConfig() { } + + private static Map makeSignalCdnUrlMapFor(SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls) { + Map result = new HashMap<>(); + result.put(0, cdn0Urls); + result.put(2, cdn2Urls); + return Collections.unmodifiableMap(result); + } } diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index ad770617..4588aaea 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.Signal; import org.asamk.signal.AttachmentInvalidException; -import org.asamk.signal.DbusConfig; import org.asamk.signal.GroupNotFoundException; import org.asamk.signal.NotAGroupMemberException; import org.asamk.signal.StickerPackInvalidException; @@ -47,6 +46,7 @@ import org.signal.libsignal.metadata.SelfSendException; import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.VerificationFailedException; +import org.signal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; @@ -75,6 +75,7 @@ import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; @@ -100,6 +101,7 @@ 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; @@ -109,6 +111,7 @@ import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; +import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.util.Base64; @@ -125,11 +128,13 @@ import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.Deque; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -236,7 +241,7 @@ public class Manager implements Signal { if (JsonGroupStore.groupsWithLegacyAvatarId.size() > 0) { for (GroupInfo g : JsonGroupStore.groupsWithLegacyAvatarId) { File avatarFile = getGroupAvatarFile(g.groupId); - File attachmentFile = getAttachmentFile(g.getAvatarId()); + File attachmentFile = getAttachmentFile(new SignalServiceAttachmentRemoteId(g.getAvatarId())); if (!avatarFile.exists() && attachmentFile.exists()) { try { IOUtils.createPrivateDirectories(avatarsPath); @@ -432,8 +437,10 @@ public class Manager implements Signal { verificationCode = verificationCode.replace("-", ""); account.setSignalingKey(KeyUtils.createSignalingKey()); // TODO make unrestricted unidentified access configurable - UUID uuid = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities); + VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities); + UUID uuid = UuidUtil.parseOrNull(response.getUuid()); + // TODO response.isStorageCapable() //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); account.setRegistered(true); account.setUuid(uuid); @@ -450,7 +457,7 @@ public class Manager implements Signal { throw new RuntimeException("Not implemented anymore, will be replaced with KBS"); } else { account.setRegistrationLockPin(null); - accountManager.removeV1Pin(); + accountManager.removeRegistrationLockV1(); } account.save(); } @@ -464,12 +471,17 @@ public class Manager implements Signal { } private SignalServiceMessageReceiver getMessageReceiver() { - return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer); + // TODO implement ZkGroup support + final ClientZkProfileOperations clientZkProfileOperations = null; + return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer, clientZkProfileOperations); } private SignalServiceMessageSender getMessageSender() { + // TODO implement ZkGroup support + final ClientZkProfileOperations clientZkProfileOperations = null; + final boolean attachmentsV3 = false; return new SignalServiceMessageSender(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), - account.getDeviceId(), account.getSignalProtocolStore(), BaseConfig.USER_AGENT, account.isMultiDevice(), Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent()); + account.getDeviceId(), account.getSignalProtocolStore(), BaseConfig.USER_AGENT, account.isMultiDevice(), attachmentsV3, Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations); } private SignalServiceProfile getRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess) throws IOException { @@ -1288,8 +1300,8 @@ public class Manager implements Signal { if (avatar.isPointer()) { try { retrieveGroupAvatarAttachment(avatar.asPointer(), group.groupId); - } catch (IOException | InvalidMessageException e) { - System.err.println("Failed to retrieve group avatar (" + avatar.asPointer().getId() + "): " + e.getMessage()); + } catch (IOException | InvalidMessageException | MissingConfigurationException e) { + System.err.println("Failed to retrieve group avatar (" + avatar.asPointer().getRemoteId() + "): " + e.getMessage()); } } } @@ -1372,8 +1384,8 @@ public class Manager implements Signal { if (attachment.isPointer()) { try { retrieveAttachment(attachment.asPointer()); - } catch (IOException | InvalidMessageException e) { - System.err.println("Failed to retrieve attachment (" + attachment.asPointer().getId() + "): " + e.getMessage()); + } catch (IOException | InvalidMessageException | MissingConfigurationException e) { + System.err.println("Failed to retrieve attachment (" + attachment.asPointer().getRemoteId() + "): " + e.getMessage()); } } } @@ -1405,8 +1417,8 @@ public class Manager implements Signal { SignalServiceAttachmentPointer attachment = preview.getImage().get().asPointer(); try { retrieveAttachment(attachment); - } catch (IOException | InvalidMessageException e) { - System.err.println("Failed to retrieve attachment (" + attachment.getId() + "): " + e.getMessage()); + } catch (IOException | InvalidMessageException | MissingConfigurationException e) { + System.err.println("Failed to retrieve attachment (" + attachment.getRemoteId() + "): " + e.getMessage()); } } } @@ -1482,7 +1494,8 @@ public class Manager implements Signal { envelope = messagePipe.read(timeout, unit, envelope1 -> { // store message on disk, before acknowledging receipt to the server try { - File cacheFile = getMessageCacheFile(envelope1.getSourceE164().get(), now, envelope1.getTimestamp()); + String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; + File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); Utils.storeEnvelope(envelope1, cacheFile); } catch (IOException e) { System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage()); @@ -1744,7 +1757,7 @@ public class Manager implements Signal { return new File(avatarsPath, "contact-" + number); } - private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException { + private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException { IOUtils.createPrivateDirectories(avatarsPath); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); @@ -1759,7 +1772,7 @@ public class Manager implements Signal { return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_")); } - private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException { + private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException { IOUtils.createPrivateDirectories(avatarsPath); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); @@ -1770,16 +1783,16 @@ public class Manager implements Signal { } } - public File getAttachmentFile(long attachmentId) { - return new File(attachmentsPath, attachmentId + ""); + public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { + return new File(attachmentsPath, attachmentId.toString()); } - private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException { + private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException { IOUtils.createPrivateDirectories(attachmentsPath); - return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true); + return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true); } - private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException { + private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException, MissingConfigurationException { if (storePreview && pointer.getPreview().isPresent()) { File previewFile = new File(outputFile + ".preview"); try (OutputStream output = new FileOutputStream(previewFile)) { @@ -1816,7 +1829,7 @@ public class Manager implements Signal { return outputFile; } - private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException { + private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException, MissingConfigurationException { final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE); } diff --git a/src/main/java/org/asamk/signal/manager/Utils.java b/src/main/java/org/asamk/signal/manager/Utils.java index 449681d2..28dd7a07 100644 --- a/src/main/java/org/asamk/signal/manager/Utils.java +++ b/src/main/java/org/asamk/signal/manager/Utils.java @@ -15,6 +15,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec; import org.whispersystems.util.Base64; import java.io.BufferedInputStream; @@ -76,10 +77,12 @@ class Utils { final long attachmentSize = attachmentFile.length(); final String mime = getFileMimeType(attachmentFile); // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option + final long uploadTimestamp = System.currentTimeMillis(); Optional preview = Optional.absent(); Optional caption = Optional.absent(); Optional blurHash = Optional.absent(); - return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, blurHash, null, null); + final Optional resumableUploadSpec = Optional.absent(); + return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, uploadTimestamp, caption, blurHash, null, null, resumableUploadSpec); } static StreamDetails createStreamDetailsFromFile(File file) throws IOException { -- 2.51.0 From 916d0e3cf1713d91c0d8c511c0f31986caad22b7 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 7 May 2020 12:41:49 +0200 Subject: [PATCH 11/16] Don't send group info request after receiving QUIT for unknown group The sender has quit the group so he won't respond to the info request anyway --- src/main/java/org/asamk/signal/manager/Manager.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 4588aaea..f76642c9 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1329,13 +1329,7 @@ public class Manager implements Signal { } break; case QUIT: - if (group == null) { - try { - sendGroupInfoRequest(groupInfo.getGroupId(), source); - } catch (IOException | EncapsulatedExceptions e) { - e.printStackTrace(); - } - } else { + if (group != null) { group.removeMember(source); account.getGroupStore().updateGroup(group); } -- 2.51.0 From 06caf4ebb3e43f0d7f1be810244bba799cd66625 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 11 May 2020 11:49:02 +0200 Subject: [PATCH 12/16] Update dependencies --- build.gradle | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 90289456..8e1c985f 100644 --- a/build.gradle +++ b/build.gradle @@ -17,11 +17,11 @@ repositories { } dependencies { - compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_8' - compile 'org.bouncycastle:bcprov-jdk15on:1.64' - compile 'net.sourceforge.argparse4j:argparse4j:0.8.1' - compile 'com.github.hypfvieh:dbus-java:3.2.0' - compile 'org.slf4j:slf4j-nop:1.7.30' + implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_8' + implementation 'org.bouncycastle:bcprov-jdk15on:1.65' + implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' + implementation 'com.github.hypfvieh:dbus-java:3.2.1' + implementation 'org.slf4j:slf4j-nop:1.7.30' } jar { @@ -41,3 +41,36 @@ run { args Eval.me(appArgs) } } + +// Find any 3rd party libraries which have released new versions +// to the central Maven repo since we last upgraded. +task checkLibVersions { + doLast { + def checked = [:] + allprojects { + configurations.each { configuration -> + configuration.allDependencies.each { dependency -> + def version = dependency.version + if (!checked[dependency]) { + def group = dependency.group + def path = group.replace('.', '/') + def name = dependency.name + def url = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" + try { + def metadata = new XmlSlurper().parseText(url.toURL().text) + def newest = metadata.versioning.latest; + if ("$version" != "$newest") { + println "UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}" + } + } catch (FileNotFoundException e) { + logger.debug "Unable to download $url: $e.message" + } catch (org.xml.sax.SAXParseException e) { + logger.debug "Unable to parse $url: $e.message" + } + checked[dependency] = true + } + } + } + } + } +} -- 2.51.0 From 8163a42d3afa269242537227be2a0fe87633ea1f Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 11 May 2020 14:51:55 +0200 Subject: [PATCH 13/16] Split manager ServiceConfig from BaseConfig --- .../java/org/asamk/signal/BaseConfig.java | 12 +++++ src/main/java/org/asamk/signal/Main.java | 4 +- .../org/asamk/signal/manager/Manager.java | 32 +++++++------ .../{BaseConfig.java => ServiceConfig.java} | 46 +++++++++---------- .../java/org/asamk/signal/manager/Utils.java | 4 +- 5 files changed, 55 insertions(+), 43 deletions(-) create mode 100644 src/main/java/org/asamk/signal/BaseConfig.java rename src/main/java/org/asamk/signal/manager/{BaseConfig.java => ServiceConfig.java} (62%) diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java new file mode 100644 index 00000000..afafc7d7 --- /dev/null +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -0,0 +1,12 @@ +package org.asamk.signal; + +public class BaseConfig { + + public final static String PROJECT_NAME = BaseConfig.class.getPackage().getImplementationTitle(); + public final static String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); + + final static String USER_AGENT = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + " " + PROJECT_VERSION; + + private BaseConfig() { + } +} diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 38c7a68d..b2defd81 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -31,8 +31,8 @@ import org.asamk.signal.commands.Commands; import org.asamk.signal.commands.DbusCommand; import org.asamk.signal.commands.ExtendedDbusCommand; import org.asamk.signal.commands.LocalCommand; -import org.asamk.signal.manager.BaseConfig; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.ServiceConfig; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.SecurityProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -102,7 +102,7 @@ public class Main { dataPath = getDefaultDataPath(); } - m = new Manager(username, dataPath); + m = new Manager(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); ts = m; try { m.init(); diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index f76642c9..ab4ac6cb 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -109,6 +109,7 @@ import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; @@ -128,13 +129,11 @@ import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.Deque; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -155,6 +154,8 @@ public class Manager implements Signal { private final String attachmentsPath; private final String avatarsPath; private final SleepTimer timer = new UptimeSleepTimer(); + private final SignalServiceConfiguration serviceConfiguration; + private final String userAgent; private SignalAccount account; private String username; @@ -162,13 +163,14 @@ public class Manager implements Signal { private SignalServiceMessagePipe messagePipe = null; private SignalServiceMessagePipe unidentifiedMessagePipe = null; - public Manager(String username, String settingsPath) { + public Manager(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) { this.username = username; this.settingsPath = settingsPath; this.dataPath = this.settingsPath + "/data"; this.attachmentsPath = this.settingsPath + "/attachments"; this.avatarsPath = this.settingsPath + "/avatars"; - + this.serviceConfiguration = serviceConfiguration; + this.userAgent = userAgent; } public String getUsername() { @@ -180,7 +182,7 @@ public class Manager implements Signal { } private SignalServiceAccountManager getSignalServiceAccountManager() { - return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer); + return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer); } private IdentityKey getIdentity() { @@ -224,7 +226,7 @@ public class Manager implements Signal { accountManager = getSignalServiceAccountManager(); if (account.isRegistered()) { - if (accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) { + if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { refreshPreKeys(); account.save(); } @@ -298,7 +300,7 @@ public class Manager implements Signal { } public void updateAccountAttributes() throws IOException { - accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities); + accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities); } public void setProfileName(String name) throws IOException { @@ -401,10 +403,10 @@ public class Manager implements Signal { } private List generatePreKeys() { - List records = new ArrayList<>(BaseConfig.PREKEY_BATCH_SIZE); + List records = new ArrayList<>(ServiceConfig.PREKEY_BATCH_SIZE); final int offset = account.getPreKeyIdOffset(); - for (int i = 0; i < BaseConfig.PREKEY_BATCH_SIZE; i++) { + for (int i = 0; i < ServiceConfig.PREKEY_BATCH_SIZE; i++) { int preKeyId = (offset + i) % Medium.MAX_VALUE; ECKeyPair keyPair = Curve.generateKeyPair(); PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair); @@ -437,7 +439,7 @@ public class Manager implements Signal { verificationCode = verificationCode.replace("-", ""); account.setSignalingKey(KeyUtils.createSignalingKey()); // TODO make unrestricted unidentified access configurable - VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities); + VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities); UUID uuid = UuidUtil.parseOrNull(response.getUuid()); // TODO response.isStorageCapable() @@ -473,15 +475,15 @@ public class Manager implements Signal { private SignalServiceMessageReceiver getMessageReceiver() { // TODO implement ZkGroup support final ClientZkProfileOperations clientZkProfileOperations = null; - return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer, clientZkProfileOperations); + return new SignalServiceMessageReceiver(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), userAgent, null, timer, clientZkProfileOperations); } private SignalServiceMessageSender getMessageSender() { // TODO implement ZkGroup support final ClientZkProfileOperations clientZkProfileOperations = null; final boolean attachmentsV3 = false; - return new SignalServiceMessageSender(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), - account.getDeviceId(), account.getSignalProtocolStore(), BaseConfig.USER_AGENT, account.isMultiDevice(), attachmentsV3, Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations); + 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); } private SignalServiceProfile getRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess) throws IOException { @@ -1801,7 +1803,7 @@ public class Manager implements Signal { final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); File tmpFile = IOUtils.createTempFile(); - try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE)) { + try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE)) { try (OutputStream output = new FileOutputStream(outputFile)) { byte[] buffer = new byte[4096]; int read; @@ -1825,7 +1827,7 @@ public class Manager implements Signal { private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException, MissingConfigurationException { final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); - return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE); + return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE); } @Override diff --git a/src/main/java/org/asamk/signal/manager/BaseConfig.java b/src/main/java/org/asamk/signal/manager/ServiceConfig.java similarity index 62% rename from src/main/java/org/asamk/signal/manager/BaseConfig.java rename to src/main/java/org/asamk/signal/manager/ServiceConfig.java index ade0288d..f1323087 100644 --- a/src/main/java/org/asamk/signal/manager/BaseConfig.java +++ b/src/main/java/org/asamk/signal/manager/ServiceConfig.java @@ -18,12 +18,8 @@ import java.util.Map; import okhttp3.Dns; import okhttp3.Interceptor; -public class BaseConfig { +public class ServiceConfig { - public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle(); - public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion(); - - final static String USER_AGENT = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + " " + PROJECT_VERSION; final static String UNIDENTIFIED_SENDER_TRUST_ROOT = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"; final static int PREKEY_MINIMUM_COUNT = 20; final static int PREKEY_BATCH_SIZE = 100; @@ -38,29 +34,28 @@ public class BaseConfig { private final static Optional dns = Optional.absent(); - private final static Interceptor userAgentInterceptor = chain -> - chain.proceed(chain.request().newBuilder() - .header("User-Agent", USER_AGENT) - .build()); - - private final static List interceptors = Collections.singletonList(userAgentInterceptor); - private final static byte[] zkGroupServerPublicParams = new byte[]{}; - final static SignalServiceConfiguration serviceConfiguration = new SignalServiceConfiguration( - new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, - makeSignalCdnUrlMapFor(new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, new SignalCdnUrl[]{new SignalCdnUrl(CDN2_URL, TRUST_STORE)}), - new SignalContactDiscoveryUrl[0], - new SignalKeyBackupServiceUrl[]{new SignalKeyBackupServiceUrl(SIGNAL_KEY_BACKUP_URL, TRUST_STORE)}, - new SignalStorageUrl[]{new SignalStorageUrl(STORAGE_URL, TRUST_STORE)}, - interceptors, - dns, - zkGroupServerPublicParams - ); - static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false, false); - private BaseConfig() { + public static SignalServiceConfiguration createDefaultServiceConfiguration(String userAgent) { + final Interceptor userAgentInterceptor = chain -> + chain.proceed(chain.request().newBuilder() + .header("User-Agent", userAgent) + .build()); + + final List interceptors = Collections.singletonList(userAgentInterceptor); + + return new SignalServiceConfiguration( + new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, new SignalCdnUrl[]{new SignalCdnUrl(CDN2_URL, TRUST_STORE)}), + new SignalContactDiscoveryUrl[0], + new SignalKeyBackupServiceUrl[]{new SignalKeyBackupServiceUrl(SIGNAL_KEY_BACKUP_URL, TRUST_STORE)}, + new SignalStorageUrl[]{new SignalStorageUrl(STORAGE_URL, TRUST_STORE)}, + interceptors, + dns, + zkGroupServerPublicParams + ); } private static Map makeSignalCdnUrlMapFor(SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls) { @@ -69,4 +64,7 @@ public class BaseConfig { result.put(2, cdn2Urls); return Collections.unmodifiableMap(result); } + + private ServiceConfig() { + } } diff --git a/src/main/java/org/asamk/signal/manager/Utils.java b/src/main/java/org/asamk/signal/manager/Utils.java index 28dd7a07..a5b37b05 100644 --- a/src/main/java/org/asamk/signal/manager/Utils.java +++ b/src/main/java/org/asamk/signal/manager/Utils.java @@ -97,7 +97,7 @@ class Utils { static CertificateValidator getCertificateValidator() { try { - ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0); + ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0); return new CertificateValidator(unidentifiedSenderTrustRoot); } catch (InvalidKeyException | IOException e) { throw new AssertionError(e); @@ -246,7 +246,7 @@ class Utils { byte[] ownId; byte[] theirId; - if (BaseConfig.capabilities.isUuid() + if (ServiceConfig.capabilities.isUuid() && ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) { // Version 2: UUID user version = 2; -- 2.51.0 From a02031aa807d45816398955c9667215baf5d06dc Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 11 May 2020 18:07:37 +0200 Subject: [PATCH 14/16] Refactor Manager to always have a valid SignalAccount instance Extract ProvisioningManager to link new devices --- src/main/java/org/asamk/signal/Main.java | 34 ++-- .../asamk/signal/commands/LinkCommand.java | 10 +- .../signal/commands/ProvisioningCommand.java | 10 + .../asamk/signal/commands/VerifyCommand.java | 4 - .../org/asamk/signal/manager/Manager.java | 185 +++++++----------- .../org/asamk/signal/manager/PathConfig.java | 34 ++++ .../signal/manager/ProvisioningManager.java | 102 ++++++++++ .../asamk/signal/storage/SignalAccount.java | 9 - 8 files changed, 239 insertions(+), 149 deletions(-) create mode 100644 src/main/java/org/asamk/signal/commands/ProvisioningCommand.java create mode 100644 src/main/java/org/asamk/signal/manager/PathConfig.java create mode 100644 src/main/java/org/asamk/signal/manager/ProvisioningManager.java diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index b2defd81..0958b9a8 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -31,7 +31,9 @@ import org.asamk.signal.commands.Commands; import org.asamk.signal.commands.DbusCommand; import org.asamk.signal.commands.ExtendedDbusCommand; import org.asamk.signal.commands.LocalCommand; +import org.asamk.signal.commands.ProvisioningCommand; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.ProvisioningManager; import org.asamk.signal.manager.ServiceConfig; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.SecurityProvider; @@ -69,13 +71,13 @@ public class Main { private static int handleCommands(Namespace ns) { final String username = ns.getString("username"); - Manager m; + Manager m = null; + ProvisioningManager pm = null; Signal ts; DBusConnection dBusConn = null; try { if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { try { - m = null; DBusConnection.DBusBusType busType; if (ns.getBoolean("dbus_system")) { busType = DBusConnection.DBusBusType.SYSTEM; @@ -102,19 +104,23 @@ public class Main { dataPath = getDefaultDataPath(); } - m = new Manager(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); - ts = m; - try { - m.init(); - } catch (AuthorizationFailedException e) { - if (!"register".equals(ns.getString("command"))) { - // Register command should still be possible, if current authorization fails - System.err.println("Authorization failed, was the number registered elsewhere?"); + if (username == null) { + pm = new ProvisioningManager(dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); + ts = null; + } else { + try { + m = Manager.init(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); + } catch (AuthorizationFailedException e) { + if (!"register".equals(ns.getString("command"))) { + // Register command should still be possible, if current authorization fails + System.err.println("Authorization failed, was the number registered elsewhere?"); + return 2; + } + } catch (Throwable e) { + System.err.println("Error loading state file: " + e.getMessage()); return 2; } - } catch (Exception e) { - System.err.println("Error loading state file: " + e.getMessage()); - return 2; + ts = m; } } @@ -135,6 +141,8 @@ public class Main { } else { if (command instanceof LocalCommand) { return ((LocalCommand) command).handleCommand(ns, m); + } else if (command instanceof ProvisioningCommand) { + return ((ProvisioningCommand) command).handleCommand(ns, pm); } else if (command instanceof DbusCommand) { return ((DbusCommand) command).handleCommand(ns, ts); } else { diff --git a/src/main/java/org/asamk/signal/commands/LinkCommand.java b/src/main/java/org/asamk/signal/commands/LinkCommand.java index 2a2d4c4b..917b674b 100644 --- a/src/main/java/org/asamk/signal/commands/LinkCommand.java +++ b/src/main/java/org/asamk/signal/commands/LinkCommand.java @@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.UserAlreadyExists; -import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.ProvisioningManager; import org.whispersystems.libsignal.InvalidKeyException; import java.io.IOException; @@ -12,7 +12,7 @@ import java.util.concurrent.TimeoutException; import static org.asamk.signal.util.ErrorUtils.handleAssertionError; -public class LinkCommand implements LocalCommand { +public class LinkCommand implements ProvisioningCommand { @Override public void attachToSubparser(final Subparser subparser) { @@ -21,15 +21,15 @@ public class LinkCommand implements LocalCommand { } @Override - public int handleCommand(final Namespace ns, final Manager m) { + public int handleCommand(final Namespace ns, final ProvisioningManager m) { String deviceName = ns.getString("name"); if (deviceName == null) { deviceName = "cli"; } try { System.out.println(m.getDeviceLinkUri()); - m.finishDeviceLink(deviceName); - System.out.println("Associated with: " + m.getUsername()); + String username = m.finishDeviceLink(deviceName); + System.out.println("Associated with: " + username); } catch (TimeoutException e) { System.err.println("Link request timed out, please try again."); return 3; diff --git a/src/main/java/org/asamk/signal/commands/ProvisioningCommand.java b/src/main/java/org/asamk/signal/commands/ProvisioningCommand.java new file mode 100644 index 00000000..12a612ff --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/ProvisioningCommand.java @@ -0,0 +1,10 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; + +import org.asamk.signal.manager.ProvisioningManager; + +public interface ProvisioningCommand extends Command { + + int handleCommand(Namespace ns, ProvisioningManager m); +} diff --git a/src/main/java/org/asamk/signal/commands/VerifyCommand.java b/src/main/java/org/asamk/signal/commands/VerifyCommand.java index 9d99c0d2..0f336325 100644 --- a/src/main/java/org/asamk/signal/commands/VerifyCommand.java +++ b/src/main/java/org/asamk/signal/commands/VerifyCommand.java @@ -20,10 +20,6 @@ public class VerifyCommand implements LocalCommand { @Override public int handleCommand(final Namespace ns, final Manager m) { - if (!m.userHasKeys()) { - System.err.println("User has no keys, first call register."); - return 1; - } if (m.isRegistered()) { System.err.println("User registration is already verified"); return 1; diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index ab4ac6cb..aa5c2dba 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -24,7 +24,6 @@ import org.asamk.signal.GroupNotFoundException; import org.asamk.signal.NotAGroupMemberException; import org.asamk.signal.StickerPackInvalidException; import org.asamk.signal.TrustLevel; -import org.asamk.signal.UserAlreadyExists; import org.asamk.signal.storage.SignalAccount; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.groups.GroupInfo; @@ -149,44 +148,40 @@ import java.util.zip.ZipFile; public class Manager implements Signal { - private final String settingsPath; - private final String dataPath; - private final String attachmentsPath; - private final String avatarsPath; private final SleepTimer timer = new UptimeSleepTimer(); private final SignalServiceConfiguration serviceConfiguration; private final String userAgent; - private SignalAccount account; - private String username; + private final SignalAccount account; + private final PathConfig pathConfig; private SignalServiceAccountManager accountManager; private SignalServiceMessagePipe messagePipe = null; private SignalServiceMessagePipe unidentifiedMessagePipe = null; - public Manager(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) { - this.username = username; - this.settingsPath = settingsPath; - this.dataPath = this.settingsPath + "/data"; - this.attachmentsPath = this.settingsPath + "/attachments"; - this.avatarsPath = this.settingsPath + "/avatars"; + public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) { + this.account = account; + this.pathConfig = pathConfig; this.serviceConfiguration = serviceConfiguration; this.userAgent = userAgent; + this.accountManager = createSignalServiceAccountManager(); + + this.account.setResolver(this::resolveSignalServiceAddress); } public String getUsername() { - return username; + return account.getUsername(); } public SignalServiceAddress getSelfAddress() { return account.getSelfAddress(); } - private SignalServiceAccountManager getSignalServiceAccountManager() { + private SignalServiceAccountManager createSignalServiceAccountManager() { return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer); } - private IdentityKey getIdentity() { - return account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(); + private IdentityKeyPair getIdentityKeyPair() { + return account.getSignalProtocolStore().getIdentityKeyPair(); } public int getDeviceId() { @@ -194,7 +189,7 @@ public class Manager implements Signal { } private String getMessageCachePath() { - return this.dataPath + "/" + username + ".d/msg-cache"; + return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache"; } private String getMessageCachePath(String sender) { @@ -211,30 +206,28 @@ public class Manager implements Signal { return new File(cachePath + "/" + now + "_" + timestamp); } - public boolean userHasKeys() { - return account != null && account.getSignalProtocolStore() != null; - } + public static Manager init(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) throws IOException { + PathConfig pathConfig = PathConfig.createDefault(settingsPath); - public void init() throws IOException { - if (!SignalAccount.userExists(dataPath, username)) { - return; - } - account = SignalAccount.load(dataPath, username); - account.setResolver(this::resolveSignalServiceAddress); + if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) { + IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair(); + int registrationId = KeyHelper.generateRegistrationId(false); - migrateLegacyConfigs(); + ProfileKey profileKey = KeyUtils.createProfileKey(); + SignalAccount account = SignalAccount.create(pathConfig.getDataPath(), username, identityKey, registrationId, profileKey); + account.save(); - accountManager = getSignalServiceAccountManager(); - if (account.isRegistered()) { - if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { - refreshPreKeys(); - account.save(); - } - if (account.getUuid() == null) { - account.setUuid(accountManager.getOwnUuid()); - account.save(); - } + return new Manager(account, pathConfig, serviceConfiguration, userAgent); } + + SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username); + + Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent); + + m.migrateLegacyConfigs(); + m.checkAccountState(); + + return m; } private void migrateLegacyConfigs() { @@ -246,7 +239,7 @@ public class Manager implements Signal { File attachmentFile = getAttachmentFile(new SignalServiceAttachmentRemoteId(g.getAvatarId())); if (!avatarFile.exists() && attachmentFile.exists()) { try { - IOUtils.createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { // Ignore @@ -263,31 +256,29 @@ public class Manager implements Signal { } } - private void createNewIdentity() throws IOException { - IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair(); - int registrationId = KeyHelper.generateRegistrationId(false); - if (username == null) { - account = SignalAccount.createTemporaryAccount(identityKey, registrationId); - account.setResolver(this::resolveSignalServiceAddress); - } else { - ProfileKey profileKey = KeyUtils.createProfileKey(); - account = SignalAccount.create(dataPath, username, identityKey, registrationId, profileKey); - account.setResolver(this::resolveSignalServiceAddress); - account.save(); + private void checkAccountState() throws IOException { + if (account.isRegistered()) { + if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { + refreshPreKeys(); + account.save(); + } + if (account.getUuid() == null) { + account.setUuid(accountManager.getOwnUuid()); + account.save(); + } } } public boolean isRegistered() { - return account != null && account.isRegistered(); + return account.isRegistered(); } public void register(boolean voiceVerification) throws IOException { - if (account == null) { - createNewIdentity(); - } account.setPassword(KeyUtils.createPassword()); + + // Resetting UUID, because registering doesn't work otherwise account.setUuid(null); - accountManager = getSignalServiceAccountManager(); + accountManager = createSignalServiceAccountManager(); if (voiceVerification) { accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent()); @@ -327,52 +318,6 @@ public class Manager implements Signal { account.save(); } - public String getDeviceLinkUri() throws TimeoutException, IOException { - if (account == null) { - createNewIdentity(); - } - account.setPassword(KeyUtils.createPassword()); - accountManager = getSignalServiceAccountManager(); - String uuid = accountManager.getNewDeviceUuid(); - - return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(uuid, getIdentity().getPublicKey())); - } - - public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists { - account.setSignalingKey(KeyUtils.createSignalingKey()); - SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(account.getSignalProtocolStore().getIdentityKeyPair(), account.getSignalingKey(), false, true, account.getSignalProtocolStore().getLocalRegistrationId(), deviceName); - - username = ret.getNumber(); - // TODO do this check before actually registering - if (SignalAccount.userExists(dataPath, username)) { - throw new UserAlreadyExists(username, SignalAccount.getFileName(dataPath, username)); - } - - // Create new account with the synced identity - byte[] profileKeyBytes = ret.getProfileKey(); - ProfileKey profileKey; - if (profileKeyBytes == null) { - profileKey = KeyUtils.createProfileKey(); - } else { - try { - profileKey = new ProfileKey(profileKeyBytes); - } catch (InvalidInputException e) { - throw new IOException("Received invalid profileKey", e); - } - } - account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey); - account.setResolver(this::resolveSignalServiceAddress); - - refreshPreKeys(); - - requestSyncGroups(); - requestSyncContacts(); - requestSyncBlocked(); - requestSyncConfiguration(); - - account.save(); - } - public List getLinkedDevices() throws IOException { List devices = accountManager.getDevices(); account.setMultiDevice(devices.size() > 1); @@ -394,7 +339,7 @@ public class Manager implements Signal { } private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException { - IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair(); + IdentityKeyPair identityKeyPair = getIdentityKeyPair(); String verificationCode = accountManager.getNewDeviceVerificationCode(); accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, Optional.of(account.getProfileKey().serialize()), verificationCode); @@ -447,7 +392,7 @@ public class Manager implements Signal { account.setRegistered(true); account.setUuid(uuid); account.setRegistrationLockPin(pin); - account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED); + account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED); refreshPreKeys(); account.save(); @@ -464,12 +409,12 @@ public class Manager implements Signal { account.save(); } - private void refreshPreKeys() throws IOException { + void refreshPreKeys() throws IOException { List oneTimePreKeys = generatePreKeys(); - final IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair(); + final IdentityKeyPair identityKeyPair = getIdentityKeyPair(); SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(identityKeyPair); - accountManager.setPreKeys(getIdentity(), signedPreKeyRecord, oneTimePreKeys); + accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys); } private SignalServiceMessageReceiver getMessageReceiver() { @@ -629,7 +574,7 @@ public class Manager implements Signal { } if (avatarFile != null) { - IOUtils.createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); File aFile = getGroupAvatarFile(g.groupId); Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } @@ -984,7 +929,7 @@ public class Manager implements Signal { } } - private void requestSyncGroups() throws IOException { + 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 { @@ -994,7 +939,7 @@ public class Manager implements Signal { } } - private void requestSyncContacts() throws IOException { + void requestSyncContacts() throws IOException { SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build(); SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); try { @@ -1004,7 +949,7 @@ public class Manager implements Signal { } } - private void requestSyncBlocked() throws IOException { + void requestSyncBlocked() throws IOException { SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED).build(); SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); try { @@ -1014,7 +959,7 @@ public class Manager implements Signal { } } - private void requestSyncConfiguration() throws IOException { + void requestSyncConfiguration() throws IOException { SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION).build(); SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); try { @@ -1750,11 +1695,11 @@ public class Manager implements Signal { } private File getContactAvatarFile(String number) { - return new File(avatarsPath, "contact-" + number); + return new File(pathConfig.getAvatarsPath(), "contact-" + number); } private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException { - IOUtils.createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); return retrieveAttachment(pointer, getContactAvatarFile(number), false); @@ -1765,11 +1710,11 @@ public class Manager implements Signal { } private File getGroupAvatarFile(byte[] groupId) { - return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_")); + return new File(pathConfig.getAvatarsPath(), "group-" + Base64.encodeBytes(groupId).replace("/", "_")); } private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException { - IOUtils.createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false); @@ -1780,11 +1725,11 @@ public class Manager implements Signal { } public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { - return new File(attachmentsPath, attachmentId.toString()); + return new File(pathConfig.getAttachmentsPath(), attachmentId.toString()); } private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException { - IOUtils.createPrivateDirectories(attachmentsPath); + IOUtils.createPrivateDirectories(pathConfig.getAttachmentsPath()); return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true); } @@ -2054,7 +1999,11 @@ public class Manager implements Signal { } public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) { - return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentity(), theirAddress, theirIdentityKey); + return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress, theirIdentityKey); + } + + void saveAccount() { + account.save(); } public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { diff --git a/src/main/java/org/asamk/signal/manager/PathConfig.java b/src/main/java/org/asamk/signal/manager/PathConfig.java new file mode 100644 index 00000000..2c2d938a --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/PathConfig.java @@ -0,0 +1,34 @@ +package org.asamk.signal.manager; + +public class PathConfig { + + private final String dataPath; + private final String attachmentsPath; + private final String avatarsPath; + + public static PathConfig createDefault(final String settingsPath) { + return new PathConfig( + settingsPath + "/data", + settingsPath + "/attachments", + settingsPath + "/avatars" + ); + } + + private PathConfig(final String dataPath, final String attachmentsPath, final String avatarsPath) { + this.dataPath = dataPath; + this.attachmentsPath = attachmentsPath; + this.avatarsPath = avatarsPath; + } + + public String getDataPath() { + return dataPath; + } + + public String getAttachmentsPath() { + return attachmentsPath; + } + + public String getAvatarsPath() { + return avatarsPath; + } +} diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java new file mode 100644 index 00000000..61d3315f --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -0,0 +1,102 @@ +/* + Copyright (C) 2015-2020 AsamK and contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package org.asamk.signal.manager; + +import org.asamk.signal.UserAlreadyExists; +import org.asamk.signal.storage.SignalAccount; +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.profiles.ProfileKey; +import org.whispersystems.libsignal.IdentityKeyPair; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.util.KeyHelper; +import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.SleepTimer; +import org.whispersystems.signalservice.api.util.UptimeSleepTimer; +import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +public class ProvisioningManager { + + private final PathConfig pathConfig; + private final SignalServiceConfiguration serviceConfiguration; + private final String userAgent; + + private final SignalServiceAccountManager accountManager; + private final IdentityKeyPair identityKey; + private final int registrationId; + private final String password; + + public ProvisioningManager(String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) { + this.pathConfig = PathConfig.createDefault(settingsPath); + this.serviceConfiguration = serviceConfiguration; + this.userAgent = userAgent; + + identityKey = KeyHelper.generateIdentityKeyPair(); + registrationId = KeyHelper.generateRegistrationId(false); + password = KeyUtils.createPassword(); + final SleepTimer timer = new UptimeSleepTimer(); + accountManager = new SignalServiceAccountManager(serviceConfiguration, null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID, userAgent, timer); + } + + public String getDeviceLinkUri() throws TimeoutException, IOException { + String deviceUuid = accountManager.getNewDeviceUuid(); + + return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey())); + } + + public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists { + String signalingKey = KeyUtils.createSignalingKey(); + SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(identityKey, signalingKey, false, true, registrationId, deviceName); + + String username = ret.getNumber(); + // TODO do this check before actually registering + if (SignalAccount.userExists(pathConfig.getDataPath(), username)) { + throw new UserAlreadyExists(username, SignalAccount.getFileName(pathConfig.getDataPath(), username)); + } + + // Create new account with the synced identity + byte[] profileKeyBytes = ret.getProfileKey(); + ProfileKey profileKey; + if (profileKeyBytes == null) { + profileKey = KeyUtils.createProfileKey(); + } else { + try { + profileKey = new ProfileKey(profileKeyBytes); + } catch (InvalidInputException e) { + throw new IOException("Received invalid profileKey", e); + } + } + SignalAccount account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), username, ret.getUuid(), password, ret.getDeviceId(), ret.getIdentity(), registrationId, signalingKey, profileKey); + account.save(); + + Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent); + + m.refreshPreKeys(); + + m.requestSyncGroups(); + m.requestSyncContacts(); + m.requestSyncBlocked(); + m.requestSyncConfiguration(); + + m.saveAccount(); + + return username; + } +} diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index f03bac3a..16ad2122 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -121,15 +121,6 @@ public class SignalAccount { return account; } - public static SignalAccount createTemporaryAccount(IdentityKeyPair identityKey, int registrationId) { - SignalAccount account = new SignalAccount(); - - account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); - account.registered = false; - - return account; - } - public static String getFileName(String dataPath, String username) { return dataPath + "/" + username; } -- 2.51.0 From 87f65de0c56735397fd6f68e77d6fe5cc915675d Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 11 May 2020 18:31:22 +0200 Subject: [PATCH 15/16] Save account state after ending session even if sending the message has failed --- 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 aa5c2dba..1340cd0a 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -699,6 +699,7 @@ public class Manager implements Signal { for (SignalServiceAddress address : signalServiceAddresses) { handleEndSession(address); } + account.save(); throw e; } } -- 2.51.0 From d520023fc76a522650b7561f2a4fc7a95fb5a04d Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 13 May 2020 23:33:40 +0200 Subject: [PATCH 16/16] Refactor Manager and SignalAccount to implement Closeable Should make sure that file lock and web socket connections are closed reliably. --- src/main/java/org/asamk/signal/Main.java | 177 ++++++++++-------- .../org/asamk/signal/manager/Manager.java | 116 ++++++------ .../signal/manager/ProvisioningManager.java | 21 ++- .../asamk/signal/storage/SignalAccount.java | 66 ++++--- 4 files changed, 220 insertions(+), 160 deletions(-) diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 0958b9a8..16d9e0dc 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -44,6 +44,7 @@ import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedE import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import java.io.File; +import java.io.IOException; import java.security.Security; import java.util.Map; @@ -71,92 +72,120 @@ public class Main { private static int handleCommands(Namespace ns) { final String username = ns.getString("username"); - Manager m = null; - ProvisioningManager pm = null; - Signal ts; - DBusConnection dBusConn = null; - try { - if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { - try { - DBusConnection.DBusBusType busType; - if (ns.getBoolean("dbus_system")) { - busType = DBusConnection.DBusBusType.SYSTEM; - } else { - busType = DBusConnection.DBusBusType.SESSION; - } - dBusConn = DBusConnection.getConnection(busType); - ts = dBusConn.getRemoteObject( + + if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { + try { + DBusConnection.DBusBusType busType; + if (ns.getBoolean("dbus_system")) { + busType = DBusConnection.DBusBusType.SYSTEM; + } else { + busType = DBusConnection.DBusBusType.SESSION; + } + try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) { + Signal ts = dBusConn.getRemoteObject( DbusConfig.SIGNAL_BUSNAME, DbusConfig.SIGNAL_OBJECTPATH, Signal.class); - } catch (UnsatisfiedLinkError e) { - System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); - return 1; - } catch (DBusException e) { - e.printStackTrace(); - if (dBusConn != null) { - dBusConn.disconnect(); - } - return 3; - } - } else { - String dataPath = ns.getString("config"); - if (isEmpty(dataPath)) { - dataPath = getDefaultDataPath(); + + return handleCommands(ns, ts, dBusConn); } + } catch (UnsatisfiedLinkError e) { + System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); + return 1; + } catch (DBusException | IOException e) { + e.printStackTrace(); + return 3; + } + } else { + String dataPath = ns.getString("config"); + if (isEmpty(dataPath)) { + dataPath = getDefaultDataPath(); + } - if (username == null) { - pm = new ProvisioningManager(dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); - ts = null; - } else { - try { - m = Manager.init(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); - } catch (AuthorizationFailedException e) { - if (!"register".equals(ns.getString("command"))) { - // Register command should still be possible, if current authorization fails - System.err.println("Authorization failed, was the number registered elsewhere?"); - return 2; - } - } catch (Throwable e) { - System.err.println("Error loading state file: " + e.getMessage()); + if (username == null) { + ProvisioningManager pm = new ProvisioningManager(dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); + return handleCommands(ns, pm); + } + + Manager manager; + try { + manager = Manager.init(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT); + } catch (Throwable e) { + System.err.println("Error loading state file: " + e.getMessage()); + return 2; + } + + try (Manager m = manager) { + try { + m.checkAccountState(); + } catch (AuthorizationFailedException e) { + if (!"register".equals(ns.getString("command"))) { + // Register command should still be possible, if current authorization fails + System.err.println("Authorization failed, was the number registered elsewhere?"); return 2; } - ts = m; + } catch (IOException e) { + System.err.println("Error while checking account: " + e.getMessage()); + return 2; } + + return handleCommands(ns, m); + } catch (IOException e) { + e.printStackTrace(); + return 3; + } + } + } + + private static int handleCommands(Namespace ns, Signal ts, DBusConnection dBusConn) { + String commandKey = ns.getString("command"); + final Map commands = Commands.getCommands(); + if (commands.containsKey(commandKey)) { + Command command = commands.get(commandKey); + + if (command instanceof ExtendedDbusCommand) { + return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn); + } else if (command instanceof DbusCommand) { + return ((DbusCommand) command).handleCommand(ns, ts); + } else { + System.err.println(commandKey + " is not yet implemented via dbus"); + return 1; } + } + return 0; + } - String commandKey = ns.getString("command"); - final Map commands = Commands.getCommands(); - if (commands.containsKey(commandKey)) { - Command command = commands.get(commandKey); - - if (dBusConn != null) { - if (command instanceof ExtendedDbusCommand) { - return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn); - } else if (command instanceof DbusCommand) { - return ((DbusCommand) command).handleCommand(ns, ts); - } else { - System.err.println(commandKey + " is not yet implemented via dbus"); - return 1; - } - } else { - if (command instanceof LocalCommand) { - return ((LocalCommand) command).handleCommand(ns, m); - } else if (command instanceof ProvisioningCommand) { - return ((ProvisioningCommand) command).handleCommand(ns, pm); - } else if (command instanceof DbusCommand) { - return ((DbusCommand) command).handleCommand(ns, ts); - } else { - System.err.println(commandKey + " is only works via dbus"); - return 1; - } - } + private static int handleCommands(Namespace ns, ProvisioningManager pm) { + String commandKey = ns.getString("command"); + final Map commands = Commands.getCommands(); + if (commands.containsKey(commandKey)) { + Command command = commands.get(commandKey); + + if (command instanceof ProvisioningCommand) { + return ((ProvisioningCommand) command).handleCommand(ns, pm); + } else { + System.err.println(commandKey + " only works with a username"); + return 1; } - return 0; - } finally { - if (dBusConn != null) { - dBusConn.disconnect(); + } + return 0; + } + + private static int handleCommands(Namespace ns, Manager m) { + String commandKey = ns.getString("command"); + final Map commands = Commands.getCommands(); + if (commands.containsKey(commandKey)) { + Command command = commands.get(commandKey); + + if (command instanceof LocalCommand) { + return ((LocalCommand) command).handleCommand(ns, m); + } else if (command instanceof DbusCommand) { + return ((DbusCommand) command).handleCommand(ns, m); + } else if (command instanceof ExtendedDbusCommand) { + System.err.println(commandKey + " only works via dbus"); } + return 1; } + return 0; } /** diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 1340cd0a..7d15b8a1 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -115,6 +115,7 @@ import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.util.Base64; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -146,7 +147,7 @@ import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -public class Manager implements Signal { +public class Manager implements Signal, Closeable { private final SleepTimer timer = new UptimeSleepTimer(); private final SignalServiceConfiguration serviceConfiguration; @@ -225,7 +226,6 @@ public class Manager implements Signal { Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent); m.migrateLegacyConfigs(); - m.checkAccountState(); return m; } @@ -256,7 +256,7 @@ public class Manager implements Signal { } } - private void checkAccountState() throws IOException { + public void checkAccountState() throws IOException { if (account.isRegistered()) { if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { refreshPreKeys(); @@ -1422,63 +1422,56 @@ public class Manager implements Signal { retryFailedReceivedMessages(handler, ignoreAttachments); final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); - try { - if (messagePipe == null) { - messagePipe = messageReceiver.createMessagePipe(); - } + if (messagePipe == null) { + messagePipe = messageReceiver.createMessagePipe(); + } - while (true) { - SignalServiceEnvelope envelope; - SignalServiceContent content = null; - Exception exception = null; - final long now = new Date().getTime(); - try { - envelope = messagePipe.read(timeout, unit, envelope1 -> { - // store message on disk, before acknowledging receipt to the server - try { - String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; - File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); - Utils.storeEnvelope(envelope1, cacheFile); - } catch (IOException e) { - System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage()); - } - }); - } catch (TimeoutException e) { - if (returnOnTimeout) - return; - continue; - } catch (InvalidVersionException e) { - System.err.println("Ignoring error: " + e.getMessage()); - continue; - } - if (!envelope.isReceipt()) { - try { - content = decryptMessage(envelope); - } catch (Exception e) { - exception = e; - } - handleMessage(envelope, content, ignoreAttachments); - } - account.save(); - if (!isMessageBlocked(envelope, content)) { - handler.handleMessage(envelope, content, exception); - } - if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) { - File cacheFile = null; + while (true) { + SignalServiceEnvelope envelope; + SignalServiceContent content = null; + Exception exception = null; + final long now = new Date().getTime(); + try { + envelope = messagePipe.read(timeout, unit, envelope1 -> { + // store message on disk, before acknowledging receipt to the server try { - cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp()); - Files.delete(cacheFile.toPath()); - // Try to delete directory if empty - new File(getMessageCachePath()).delete(); + String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; + File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); + Utils.storeEnvelope(envelope1, cacheFile); } catch (IOException e) { - System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage()); + System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage()); } + }); + } catch (TimeoutException e) { + if (returnOnTimeout) + return; + continue; + } catch (InvalidVersionException e) { + System.err.println("Ignoring error: " + e.getMessage()); + continue; + } + if (!envelope.isReceipt()) { + try { + content = decryptMessage(envelope); + } catch (Exception e) { + exception = e; } + handleMessage(envelope, content, ignoreAttachments); } - } finally { - if (messagePipe != null) { - messagePipe.shutdown(); - messagePipe = null; + account.save(); + if (!isMessageBlocked(envelope, content)) { + handler.handleMessage(envelope, content, exception); + } + if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) { + File cacheFile = null; + try { + cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp()); + Files.delete(cacheFile.toPath()); + // Try to delete directory if empty + new File(getMessageCachePath()).delete(); + } catch (IOException e) { + System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage()); + } } } } @@ -2026,6 +2019,21 @@ public class Manager implements Signal { return account.getRecipientStore().resolveServiceAddress(address); } + @Override + public void close() throws IOException { + if (messagePipe != null) { + messagePipe.shutdown(); + messagePipe = null; + } + + if (unidentifiedMessagePipe != null) { + unidentifiedMessagePipe.shutdown(); + unidentifiedMessagePipe = null; + } + + account.close(); + } + public interface ReceiveMessageHandler { void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e); diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index 61d3315f..e7693f21 100644 --- a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -83,19 +83,22 @@ public class ProvisioningManager { throw new IOException("Received invalid profileKey", e); } } - SignalAccount account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), username, ret.getUuid(), password, ret.getDeviceId(), ret.getIdentity(), registrationId, signalingKey, profileKey); - account.save(); - Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent); + try (SignalAccount account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), username, ret.getUuid(), password, ret.getDeviceId(), ret.getIdentity(), registrationId, signalingKey, profileKey)) { + account.save(); - m.refreshPreKeys(); + try (Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent)) { - m.requestSyncGroups(); - m.requestSyncContacts(); - m.requestSyncBlocked(); - m.requestSyncConfiguration(); + m.refreshPreKeys(); - m.saveAccount(); + m.requestSyncGroups(); + m.requestSyncContacts(); + m.requestSyncBlocked(); + m.requestSyncConfiguration(); + + m.saveAccount(); + } + } return username; } diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index 16ad2122..94935441 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -29,9 +29,11 @@ import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.Medium; +import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.util.Base64; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -42,11 +44,11 @@ import java.util.Collection; import java.util.UUID; import java.util.stream.Collectors; -public class SignalAccount { +public class SignalAccount implements Closeable { private final ObjectMapper jsonProcessor = new ObjectMapper(); - private FileChannel fileChannel; - private FileLock lock; + private final FileChannel fileChannel; + private final FileLock lock; private String username; private UUID uuid; private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; @@ -65,7 +67,9 @@ public class SignalAccount { private JsonContactsStore contactStore; private RecipientStore recipientStore; - private SignalAccount() { + private SignalAccount(final FileChannel fileChannel, final FileLock lock) { + this.fileChannel = fileChannel; + this.lock = lock; jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect jsonProcessor.enable(SerializationFeature.INDENT_OUTPUT); // for pretty print, you can disable it. jsonProcessor.enable(SerializationFeature.WRITE_NULL_MAP_VALUES); @@ -75,18 +79,28 @@ public class SignalAccount { } public static SignalAccount load(String dataPath, String username) throws IOException { - SignalAccount account = new SignalAccount(); - IOUtils.createPrivateDirectories(dataPath); - account.openFileChannel(getFileName(dataPath, username)); - account.load(); - return account; + final String fileName = getFileName(dataPath, username); + final Pair pair = openFileChannel(fileName); + try { + SignalAccount account = new SignalAccount(pair.first(), pair.second()); + account.load(); + return account; + } catch (Throwable e) { + pair.second().close(); + pair.first().close(); + throw e; + } } public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey) throws IOException { IOUtils.createPrivateDirectories(dataPath); + String fileName = getFileName(dataPath, username); + if (!new File(fileName).exists()) { + IOUtils.createPrivateFile(fileName); + } - SignalAccount account = new SignalAccount(); - account.openFileChannel(getFileName(dataPath, username)); + final Pair pair = openFileChannel(fileName); + SignalAccount account = new SignalAccount(pair.first(), pair.second()); account.username = username; account.profileKey = profileKey; @@ -101,9 +115,13 @@ public class SignalAccount { public static SignalAccount createLinkedAccount(String dataPath, String username, UUID uuid, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException { IOUtils.createPrivateDirectories(dataPath); + String fileName = getFileName(dataPath, username); + if (!new File(fileName).exists()) { + IOUtils.createPrivateFile(fileName); + } - SignalAccount account = new SignalAccount(); - account.openFileChannel(getFileName(dataPath, username)); + final Pair pair = openFileChannel(fileName); + SignalAccount account = new SignalAccount(pair.first(), pair.second()); account.username = username; account.uuid = uuid; @@ -285,21 +303,15 @@ public class SignalAccount { } } - private void openFileChannel(String fileName) throws IOException { - if (fileChannel != null) { - return; - } - - if (!new File(fileName).exists()) { - IOUtils.createPrivateFile(fileName); - } - fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel(); - lock = fileChannel.tryLock(); + private static Pair openFileChannel(String fileName) throws IOException { + FileChannel fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel(); + FileLock lock = fileChannel.tryLock(); if (lock == null) { System.err.println("Config file is in use by another instance, waiting…"); lock = fileChannel.lock(); System.err.println("Config file lock acquired."); } + return new Pair<>(fileChannel, lock); } public void setResolver(final SignalServiceAddressResolver resolver) { @@ -413,4 +425,12 @@ public class SignalAccount { public void setMultiDevice(final boolean multiDevice) { isMultiDevice = multiDevice; } + + @Override + public void close() throws IOException { + synchronized (fileChannel) { + lock.close(); + fileChannel.close(); + } + } } -- 2.51.0