From f60a10eb6e40921c32045c1e38843d6b87f3d274 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Nov 2018 23:19:39 +0100 Subject: [PATCH] Split commands into separate classes --- src/main/java/org/asamk/Signal.java | 2 + .../java/org/asamk/signal/DbusConfig.java | 7 + .../signal/DbusReceiveMessageHandler.java | 4 +- .../signal/JsonDbusReceiveMessageHandler.java | 4 +- .../signal/JsonReceiveMessageHandler.java | 4 +- src/main/java/org/asamk/signal/Main.java | 783 ++---------------- .../asamk/signal/ReceiveMessageHandler.java | 4 +- .../signal/commands/AddDeviceCommand.java | 43 + .../org/asamk/signal/commands/Command.java | 8 + .../org/asamk/signal/commands/Commands.java | 38 + .../asamk/signal/commands/DaemonCommand.java | 76 ++ .../asamk/signal/commands/DbusCommand.java | 9 + .../signal/commands/ExtendedDbusCommand.java | 10 + .../asamk/signal/commands/LinkCommand.java | 50 ++ .../signal/commands/ListDevicesCommand.java | 38 + .../signal/commands/ListGroupsCommand.java | 46 + .../commands/ListIdentitiesCommand.java | 47 ++ .../asamk/signal/commands/LocalCommand.java | 9 + .../signal/commands/QuitGroupCommand.java | 55 ++ .../asamk/signal/commands/ReceiveCommand.java | 110 +++ .../signal/commands/RegisterCommand.java | 29 + .../signal/commands/RemoveDeviceCommand.java | 34 + .../signal/commands/RemovePinCommand.java | 30 + .../asamk/signal/commands/SendCommand.java | 124 +++ .../asamk/signal/commands/SetPinCommand.java | 33 + .../asamk/signal/commands/TrustCommand.java | 74 ++ .../signal/commands/UnregisterCommand.java | 30 + .../signal/commands/UpdateAccountCommand.java | 30 + .../signal/commands/UpdateGroupCommand.java | 88 ++ .../asamk/signal/commands/VerifyCommand.java | 44 + 30 files changed, 1121 insertions(+), 742 deletions(-) create mode 100644 src/main/java/org/asamk/signal/DbusConfig.java create mode 100644 src/main/java/org/asamk/signal/commands/AddDeviceCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/Command.java create mode 100644 src/main/java/org/asamk/signal/commands/Commands.java create mode 100644 src/main/java/org/asamk/signal/commands/DaemonCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/DbusCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/LinkCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/ListDevicesCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/ListGroupsCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/LocalCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/QuitGroupCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/ReceiveCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/RegisterCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/RemovePinCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/SendCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/SetPinCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/TrustCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/UnregisterCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java create mode 100644 src/main/java/org/asamk/signal/commands/VerifyCommand.java diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 7c311813..6fb59303 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -32,6 +32,8 @@ public interface Signal extends DBusInterface { byte[] updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException; + boolean isRegistered(); + class MessageReceived extends DBusSignal { private long timestamp; diff --git a/src/main/java/org/asamk/signal/DbusConfig.java b/src/main/java/org/asamk/signal/DbusConfig.java new file mode 100644 index 00000000..c0d23175 --- /dev/null +++ b/src/main/java/org/asamk/signal/DbusConfig.java @@ -0,0 +1,7 @@ +package org.asamk.signal; + +public class DbusConfig { + + public static final String SIGNAL_BUSNAME = "org.asamk.Signal"; + public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal"; +} diff --git a/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java index 2ea51e2e..cebabc18 100644 --- a/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/DbusReceiveMessageHandler.java @@ -5,12 +5,12 @@ import org.freedesktop.dbus.DBusConnection; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -class DbusReceiveMessageHandler extends ReceiveMessageHandler { +public class DbusReceiveMessageHandler extends ReceiveMessageHandler { private final DBusConnection conn; private final String objectPath; - DbusReceiveMessageHandler(Manager m, DBusConnection conn, final String objectPath) { + public DbusReceiveMessageHandler(Manager m, DBusConnection conn, final String objectPath) { super(m); this.conn = conn; this.objectPath = objectPath; diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 533a1aed..c0977c0f 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -9,13 +9,13 @@ import org.whispersystems.signalservice.api.messages.*; import java.util.ArrayList; import java.util.List; -class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { +public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { private final DBusConnection conn; private final String objectPath; - JsonDbusReceiveMessageHandler(Manager m, DBusConnection conn, final String objectPath) { + public JsonDbusReceiveMessageHandler(Manager m, DBusConnection conn, final String objectPath) { super(m); this.conn = conn; this.objectPath = objectPath; diff --git a/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java index 7ae5d45d..cbfe72bd 100644 --- a/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java @@ -13,12 +13,12 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import java.io.IOException; -class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler { +public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler { final Manager m; private final ObjectMapper jsonProcessor; - JsonReceiveMessageHandler(Manager m) { + public JsonReceiveMessageHandler(Manager m) { this.m = m; this.jsonProcessor = new ObjectMapper(); jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // disable autodetect diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 604c51ee..a0820f24 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -21,46 +21,19 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.*; import org.apache.http.util.TextUtils; import org.asamk.Signal; +import org.asamk.signal.commands.*; import org.asamk.signal.manager.BaseConfig; import org.asamk.signal.manager.Manager; -import org.asamk.signal.storage.groups.GroupInfo; -import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; -import org.asamk.signal.util.DateUtils; -import org.asamk.signal.util.Hex; -import org.asamk.signal.util.IOUtils; -import org.asamk.signal.util.Util; import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.DBusSigHandler; import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; -import org.whispersystems.signalservice.internal.push.LockedException; -import org.whispersystems.signalservice.internal.util.Base64; import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.Charset; import java.security.Security; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static org.asamk.signal.util.ErrorUtils.*; public class Main { - private static final String SIGNAL_BUSNAME = "org.asamk.Signal"; - private static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal"; - public static void main(String[] args) { // Workaround for BKS truststore Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1); @@ -91,7 +64,7 @@ public class Main { } dBusConn = DBusConnection.getConnection(busType); ts = dBusConn.getRemoteObject( - SIGNAL_BUSNAME, SIGNAL_OBJECTPATH, + DbusConfig.SIGNAL_BUSNAME, DbusConfig.SIGNAL_OBJECTPATH, Signal.class); } catch (UnsatisfiedLinkError e) { System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); @@ -125,567 +98,30 @@ public class Main { } } - switch (ns.getString("command")) { - case "register": - if (dBusConn != null) { - System.err.println("register is not yet implemented via dbus"); - return 1; - } - try { - m.register(ns.getBoolean("voice")); - } catch (IOException e) { - System.err.println("Request verify error: " + e.getMessage()); - return 3; - } - break; - case "unregister": - if (dBusConn != null) { - System.err.println("unregister is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - m.unregister(); - } catch (IOException e) { - System.err.println("Unregister error: " + e.getMessage()); - return 3; - } - break; - case "updateAccount": - if (dBusConn != null) { - System.err.println("updateAccount is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - m.updateAccountAttributes(); - } catch (IOException e) { - System.err.println("UpdateAccount error: " + e.getMessage()); - return 3; - } - break; - case "setPin": - if (dBusConn != null) { - System.err.println("setPin is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - String registrationLockPin = ns.getString("registrationLockPin"); - m.setRegistrationLockPin(Optional.of(registrationLockPin)); - } catch (IOException e) { - System.err.println("Set pin error: " + e.getMessage()); - return 3; - } - break; - case "removePin": - if (dBusConn != null) { - System.err.println("removePin is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - m.setRegistrationLockPin(Optional.absent()); - } catch (IOException e) { - System.err.println("Remove pin error: " + e.getMessage()); - return 3; - } - break; - case "verify": - if (dBusConn != null) { - System.err.println("verify is not yet implemented via dbus"); - return 1; - } - 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; - } - try { - String verificationCode = ns.getString("verificationCode"); - String pin = ns.getString("pin"); - m.verifyAccount(verificationCode, pin); - } catch (LockedException e) { - System.err.println("Verification failed! This number is locked with a pin. Hours remaining until reset: " + (e.getTimeRemaining() / 1000 / 60 / 60)); - System.err.println("Use '--pin PIN_CODE' to specify the registration lock PIN"); - return 3; - } catch (IOException e) { - System.err.println("Verify error: " + e.getMessage()); - return 3; - } - break; - case "link": - if (dBusConn != null) { - System.err.println("link is not yet implemented via dbus"); - return 1; - } + String commandKey = ns.getString("command"); + final Map commands = Commands.getCommands(); + if (commands.containsKey(commandKey)) { + Command command = commands.get(commandKey); - 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()); - } catch (TimeoutException e) { - System.err.println("Link request timed out, please try again."); - return 3; - } catch (IOException e) { - System.err.println("Link request error: " + e.getMessage()); - return 3; - } catch (AssertionError e) { - handleAssertionError(e); - return 1; - } catch (InvalidKeyException e) { - e.printStackTrace(); - return 2; - } catch (UserAlreadyExists e) { - System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again."); - return 1; - } - break; - case "addDevice": - if (dBusConn != null) { - System.err.println("link is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - m.addDeviceLink(new URI(ns.getString("uri"))); - } catch (IOException e) { - e.printStackTrace(); - return 3; - } catch (InvalidKeyException | URISyntaxException e) { - e.printStackTrace(); - return 2; - } catch (AssertionError e) { - handleAssertionError(e); - return 1; - } - break; - case "listDevices": - if (dBusConn != null) { - System.err.println("listDevices is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - List devices = m.getLinkedDevices(); - for (DeviceInfo d : devices) { - System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":"); - System.out.println(" Name: " + d.getName()); - System.out.println(" Created: " + DateUtils.formatTimestamp(d.getCreated())); - System.out.println(" Last seen: " + DateUtils.formatTimestamp(d.getLastSeen())); - } - } catch (IOException e) { - e.printStackTrace(); - return 3; - } - break; - case "removeDevice": - if (dBusConn != null) { - System.err.println("removeDevice is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - try { - int deviceId = ns.getInt("deviceId"); - m.removeLinkedDevices(deviceId); - } catch (IOException e) { - e.printStackTrace(); - return 3; - } - break; - case "send": - if (dBusConn == null && !m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - - if (ns.getBoolean("endsession")) { - if (ns.getList("recipient") == null) { - System.err.println("No recipients given"); - System.err.println("Aborting sending."); - return 1; - } - try { - ts.sendEndSessionMessage(ns.getList("recipient")); - } catch (IOException e) { - handleIOException(e); - return 3; - } catch (EncapsulatedExceptions e) { - handleEncapsulatedExceptions(e); - return 3; - } catch (AssertionError e) { - handleAssertionError(e); - return 1; - } catch (DBusExecutionException e) { - handleDBusExecutionException(e); - return 1; - } + 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 { - String messageText = ns.getString("message"); - if (messageText == null) { - try { - messageText = IOUtils.readAll(System.in, Charset.defaultCharset()); - } catch (IOException e) { - System.err.println("Failed to read message from stdin: " + e.getMessage()); - System.err.println("Aborting sending."); - return 1; - } - } - - try { - List attachments = ns.getList("attachment"); - if (attachments == null) { - attachments = new ArrayList<>(); - } - if (ns.getString("group") != null) { - byte[] groupId = Util.decodeGroupId(ns.getString("group")); - ts.sendGroupMessage(messageText, attachments, groupId); - } else { - ts.sendMessage(messageText, attachments, ns.getList("recipient")); - } - } catch (IOException e) { - handleIOException(e); - return 3; - } catch (EncapsulatedExceptions e) { - handleEncapsulatedExceptions(e); - return 3; - } catch (AssertionError e) { - handleAssertionError(e); - return 1; - } catch (GroupNotFoundException e) { - handleGroupNotFoundException(e); - return 1; - } catch (NotAGroupMemberException e) { - handleNotAGroupMemberException(e); - return 1; - } catch (AttachmentInvalidException e) { - System.err.println("Failed to add attachment: " + e.getMessage()); - System.err.println("Aborting sending."); - return 1; - } catch (DBusExecutionException e) { - handleDBusExecutionException(e); - return 1; - } catch (GroupIdFormatException e) { - handleGroupIdFormatException(e); - return 1; - } - } - - break; - case "receive": - if (dBusConn != null) { - try { - dBusConn.addSigHandler(Signal.MessageReceived.class, new DBusSigHandler() { - @Override - public void handle(Signal.MessageReceived s) { - System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n", - s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()), s.getMessage())); - if (s.getGroupId().length > 0) { - System.out.println("Group info:"); - System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId())); - } - if (s.getAttachments().size() > 0) { - System.out.println("Attachments: "); - for (String attachment : s.getAttachments()) { - System.out.println("- Stored plaintext in: " + attachment); - } - } - System.out.println(); - } - }); - dBusConn.addSigHandler(Signal.ReceiptReceived.class, new DBusSigHandler() { - @Override - public void handle(Signal.ReceiptReceived s) { - System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n", - s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()))); - } - }); - } 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; - } - } - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - double timeout = 5; - if (ns.getDouble("timeout") != null) { - timeout = ns.getDouble("timeout"); - } - boolean returnOnTimeout = true; - if (timeout < 0) { - returnOnTimeout = false; - timeout = 3600; - } - boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); - try { - final Manager.ReceiveMessageHandler handler = ns.getBoolean("json") ? new JsonReceiveMessageHandler(m) : new ReceiveMessageHandler(m); - m.receiveMessages((long) (timeout * 1000), TimeUnit.MILLISECONDS, returnOnTimeout, ignoreAttachments, handler); - } catch (IOException e) { - System.err.println("Error while receiving messages: " + e.getMessage()); - return 3; - } catch (AssertionError e) { - handleAssertionError(e); + System.err.println(commandKey + " is not yet implemented via dbus"); return 1; } - break; - case "quitGroup": - if (dBusConn != null) { - System.err.println("quitGroup is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - - try { - m.sendQuitGroupMessage(Util.decodeGroupId(ns.getString("group"))); - } catch (IOException e) { - handleIOException(e); - return 3; - } catch (EncapsulatedExceptions e) { - handleEncapsulatedExceptions(e); - return 3; - } catch (AssertionError e) { - handleAssertionError(e); - return 1; - } catch (GroupNotFoundException e) { - handleGroupNotFoundException(e); - return 1; - } catch (NotAGroupMemberException e) { - handleNotAGroupMemberException(e); - return 1; - } catch (GroupIdFormatException e) { - handleGroupIdFormatException(e); - return 1; - } - - break; - case "updateGroup": - if (dBusConn == null && !m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - - try { - byte[] groupId = null; - if (ns.getString("group") != null) { - groupId = Util.decodeGroupId(ns.getString("group")); - } - if (groupId == null) { - groupId = new byte[0]; - } - String groupName = ns.getString("name"); - if (groupName == null) { - groupName = ""; - } - List groupMembers = ns.getList("member"); - if (groupMembers == null) { - groupMembers = new ArrayList<>(); - } - String groupAvatar = ns.getString("avatar"); - if (groupAvatar == null) { - groupAvatar = ""; - } - byte[] newGroupId = ts.updateGroup(groupId, groupName, groupMembers, groupAvatar); - if (groupId.length != newGroupId.length) { - System.out.println("Creating new group \"" + Base64.encodeBytes(newGroupId) + "\" …"); - } - } catch (IOException e) { - handleIOException(e); - return 3; - } catch (AttachmentInvalidException e) { - System.err.println("Failed to add avatar attachment for group\": " + e.getMessage()); - System.err.println("Aborting sending."); - return 1; - } catch (GroupNotFoundException e) { - handleGroupNotFoundException(e); - return 1; - } catch (NotAGroupMemberException e) { - handleNotAGroupMemberException(e); - return 1; - } catch (EncapsulatedExceptions e) { - handleEncapsulatedExceptions(e); - return 3; - } catch (GroupIdFormatException e) { - handleGroupIdFormatException(e); - return 1; - } - - break; - case "listGroups": - if (dBusConn != null) { - System.err.println("listGroups is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - - List groups = m.getGroups(); - boolean detailed = ns.getBoolean("detailed"); - - for (GroupInfo group : groups) { - printGroup(group, detailed); - } - break; - case "listIdentities": - if (dBusConn != null) { - System.err.println("listIdentities is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - if (ns.get("number") == null) { - for (Map.Entry> keys : m.getIdentities().entrySet()) { - for (JsonIdentityKeyStore.Identity id : keys.getValue()) { - printIdentityFingerprint(m, keys.getKey(), id); - } - } + } else { + if (command instanceof LocalCommand) { + return ((LocalCommand) command).handleCommand(ns, m); + } else if (command instanceof DbusCommand) { + return ((DbusCommand) command).handleCommand(ns, ts); } else { - String number = ns.getString("number"); - for (JsonIdentityKeyStore.Identity id : m.getIdentities(number)) { - printIdentityFingerprint(m, number, id); - } - } - break; - case "trust": - if (dBusConn != null) { - System.err.println("trust is not yet implemented via dbus"); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); + System.err.println(commandKey + " is only works via dbus"); return 1; } - String number = ns.getString("number"); - if (ns.getBoolean("trust_all_known_keys")) { - boolean res = m.trustIdentityAllKeys(number); - if (!res) { - System.err.println("Failed to set the trust for this number, make sure the number is correct."); - return 1; - } - } else { - String fingerprint = ns.getString("verified_fingerprint"); - if (fingerprint != null) { - fingerprint = fingerprint.replaceAll(" ", ""); - if (fingerprint.length() == 66) { - byte[] fingerprintBytes; - try { - fingerprintBytes = Hex.toByteArray(fingerprint.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; - } - boolean res = m.trustIdentityVerified(number, fingerprintBytes); - if (!res) { - 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) { - boolean res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint); - if (!res) { - System.err.println("Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct."); - return 1; - } - } else { - System.err.println("Fingerprint 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"); - return 1; - } - } - break; - case "daemon": - if (dBusConn != null) { - System.err.println("Stop it."); - return 1; - } - if (!m.isRegistered()) { - System.err.println("User is not registered."); - return 1; - } - DBusConnection conn = null; - try { - try { - int busType; - if (ns.getBoolean("system")) { - busType = DBusConnection.SYSTEM; - } else { - busType = DBusConnection.SESSION; - } - conn = DBusConnection.getConnection(busType); - conn.exportObject(SIGNAL_OBJECTPATH, m); - conn.requestBusName(SIGNAL_BUSNAME); - } catch (UnsatisfiedLinkError e) { - System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); - return 1; - } catch (DBusException e) { - e.printStackTrace(); - return 2; - } - ignoreAttachments = ns.getBoolean("ignore_attachments"); - try { - m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, ns.getBoolean("json") ? new JsonDbusReceiveMessageHandler(m, conn, Main.SIGNAL_OBJECTPATH) : new DbusReceiveMessageHandler(m, conn, Main.SIGNAL_OBJECTPATH)); - } catch (IOException e) { - System.err.println("Error while receiving messages: " + e.getMessage()); - return 3; - } catch (AssertionError e) { - handleAssertionError(e); - return 1; - } - } finally { - if (conn != null) { - conn.disconnect(); - } - } - - break; + } } return 0; } finally { @@ -695,22 +131,6 @@ public class Main { } } - private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) { - String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey())); - System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername, - theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits)); - } - - private static void printGroup(GroupInfo group, boolean detailed) { - if (detailed) { - System.out.println(String.format("Id: %s Name: %s Active: %s Members: %s", - Base64.encodeBytes(group.groupId), group.name, group.active, group.members)); - } else { - System.out.println(String.format("Id: %s Name: %s Active: %s", Base64.encodeBytes(group.groupId), - group.name, group.active)); - } - } - private static Namespace parseArgs(String[] args) { ArgumentParser parser = ArgumentParsers.newFor("signal-cli") .build() @@ -740,146 +160,41 @@ public class Main { .description("valid subcommands") .help("additional help"); - Subparser parserLink = subparsers.addParser("link"); - parserLink.addArgument("-n", "--name") - .help("Specify a name to describe this new device."); - - Subparser parserAddDevice = subparsers.addParser("addDevice"); - parserAddDevice.addArgument("--uri") - .required(true) - .help("Specify the uri contained in the QR code shown by the new device."); - - Subparser parserDevices = subparsers.addParser("listDevices"); - - Subparser parserRemoveDevice = subparsers.addParser("removeDevice"); - parserRemoveDevice.addArgument("-d", "--deviceId") - .type(int.class) - .required(true) - .help("Specify the device you want to remove. Use listDevices to see the deviceIds."); - - Subparser parserRegister = subparsers.addParser("register"); - parserRegister.addArgument("-v", "--voice") - .help("The verification should be done over voice, not sms.") - .action(Arguments.storeTrue()); - - Subparser parserUnregister = subparsers.addParser("unregister"); - parserUnregister.help("Unregister the current device from the signal server."); - - Subparser parserUpdateAccount = subparsers.addParser("updateAccount"); - parserUpdateAccount.help("Update the account attributes on the signal server."); - - Subparser parserSetPin = subparsers.addParser("setPin"); - parserSetPin.addArgument("registrationLockPin") - .help("The registration lock PIN, that will be required for new registrations (resets after 7 days of inactivity)"); - - Subparser parserRemovePin = subparsers.addParser("removePin"); - - Subparser parserVerify = subparsers.addParser("verify"); - parserVerify.addArgument("verificationCode") - .help("The verification code you received via sms or voice call."); - parserVerify.addArgument("-p", "--pin") - .help("The registration lock PIN, that was set by the user (Optional)"); - - Subparser parserSend = subparsers.addParser("send"); - parserSend.addArgument("-g", "--group") - .help("Specify the recipient group ID."); - parserSend.addArgument("recipient") - .help("Specify the recipients' phone number.") - .nargs("*"); - parserSend.addArgument("-m", "--message") - .help("Specify the message, if missing standard input is used."); - parserSend.addArgument("-a", "--attachment") - .nargs("*") - .help("Add file as attachment"); - parserSend.addArgument("-e", "--endsession") - .help("Clear session state and send end session message.") - .action(Arguments.storeTrue()); - - Subparser parserLeaveGroup = subparsers.addParser("quitGroup"); - parserLeaveGroup.addArgument("-g", "--group") - .required(true) - .help("Specify the recipient group ID."); - - Subparser parserUpdateGroup = subparsers.addParser("updateGroup"); - parserUpdateGroup.addArgument("-g", "--group") - .help("Specify the recipient group ID."); - parserUpdateGroup.addArgument("-n", "--name") - .help("Specify the new group name."); - parserUpdateGroup.addArgument("-a", "--avatar") - .help("Specify a new group avatar image file"); - parserUpdateGroup.addArgument("-m", "--member") - .nargs("*") - .help("Specify one or more members to add to the group"); - - Subparser parserListGroups = subparsers.addParser("listGroups"); - parserListGroups.addArgument("-d", "--detailed").action(Arguments.storeTrue()) - .help("List members of each group"); - parserListGroups.help("List group name and ids"); - - Subparser parserListIdentities = subparsers.addParser("listIdentities"); - parserListIdentities.addArgument("-n", "--number") - .help("Only show identity keys for the given phone number."); - - Subparser parserTrust = subparsers.addParser("trust"); - parserTrust.addArgument("number") - .help("Specify the phone number, for which to set the trust.") - .required(true); - MutuallyExclusiveGroup mutTrust = parserTrust.addMutuallyExclusiveGroup(); - 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."); - - Subparser parserReceive = subparsers.addParser("receive"); - parserReceive.addArgument("-t", "--timeout") - .type(double.class) - .help("Number of seconds to wait for new messages (negative values disable timeout)"); - parserReceive.addArgument("--ignore-attachments") - .help("Don’t download attachments of received messages.") - .action(Arguments.storeTrue()); - parserReceive.addArgument("--json") - .help("Output received messages in json format, one json object per line.") - .action(Arguments.storeTrue()); - - Subparser parserDaemon = subparsers.addParser("daemon"); - parserDaemon.addArgument("--system") - .action(Arguments.storeTrue()) - .help("Use DBus system bus instead of user bus."); - parserDaemon.addArgument("--ignore-attachments") - .help("Don’t download attachments of received messages.") - .action(Arguments.storeTrue()); - parserDaemon.addArgument("--json") - .help("Output received messages in json format, one json object per line.") - .action(Arguments.storeTrue()); + final Map commands = Commands.getCommands(); + for (Map.Entry entry : commands.entrySet()) { + Subparser subparser = subparsers.addParser(entry.getKey()); + entry.getValue().attachToSubparser(subparser); + } + Namespace ns; try { - Namespace ns = parser.parseArgs(args); - if ("link".equals(ns.getString("command"))) { - if (ns.getString("username") != null) { - parser.printUsage(); - System.err.println("You cannot specify a username (phone number) when linking"); - System.exit(2); - } - } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) { - if (ns.getString("username") == null) { - parser.printUsage(); - System.err.println("You need to specify a username (phone number)"); - System.exit(2); - } - if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"))) { - System.err.println("Invalid username (phone number), make sure you include the country code."); - System.exit(2); - } - } - if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) { - System.err.println("You cannot specify recipients by phone number and groups a the same time"); - System.exit(2); - } - return ns; + ns = parser.parseArgs(args); } catch (ArgumentParserException e) { parser.handleError(e); return null; } + + if ("link".equals(ns.getString("command"))) { + if (ns.getString("username") != null) { + parser.printUsage(); + System.err.println("You cannot specify a username (phone number) when linking"); + System.exit(2); + } + } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) { + if (ns.getString("username") == null) { + parser.printUsage(); + System.err.println("You need to specify a username (phone number)"); + System.exit(2); + } + if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"))) { + System.err.println("Invalid username (phone number), make sure you include the country code."); + System.exit(2); + } + } + if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) { + System.err.println("You cannot specify recipients by phone number and groups a the same time"); + System.exit(2); + } + return ns; } } diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 5c578536..f8c93f52 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -14,11 +14,11 @@ import org.whispersystems.signalservice.internal.util.Base64; import java.io.File; import java.util.List; -class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { +public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final Manager m; - ReceiveMessageHandler(Manager m) { + public ReceiveMessageHandler(Manager m) { this.m = m; } diff --git a/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java b/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java new file mode 100644 index 00000000..c4402696 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java @@ -0,0 +1,43 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.whispersystems.libsignal.InvalidKeyException; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.asamk.signal.util.ErrorUtils.handleAssertionError; + +public class AddDeviceCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("--uri") + .required(true) + .help("Specify the uri contained in the QR code shown by the new device."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + m.addDeviceLink(new URI(ns.getString("uri"))); + return 0; + } catch (IOException e) { + e.printStackTrace(); + return 3; + } catch (InvalidKeyException | URISyntaxException e) { + e.printStackTrace(); + return 2; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/Command.java b/src/main/java/org/asamk/signal/commands/Command.java new file mode 100644 index 00000000..1e4abc19 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/Command.java @@ -0,0 +1,8 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Subparser; + +public interface Command { + + void attachToSubparser(Subparser subparser); +} diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java new file mode 100644 index 00000000..6f262fdf --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -0,0 +1,38 @@ +package org.asamk.signal.commands; + +import java.util.HashMap; +import java.util.Map; + +public class Commands { + + private static final Map commands = new HashMap<>(); + + static { + addCommand("addDevice", new AddDeviceCommand()); + addCommand("daemon", new DaemonCommand()); + addCommand("link", new LinkCommand()); + addCommand("listDevices", new ListDevicesCommand()); + addCommand("listGroups", new ListGroupsCommand()); + addCommand("listIdentities", new ListIdentitiesCommand()); + addCommand("quitGroup", new QuitGroupCommand()); + addCommand("receive", new ReceiveCommand()); + addCommand("register", new RegisterCommand()); + addCommand("removeDevice", new RemoveDeviceCommand()); + addCommand("removePin", new RemovePinCommand()); + addCommand("send", new SendCommand()); + addCommand("setPin", new SetPinCommand()); + addCommand("trust", new TrustCommand()); + addCommand("unregister", new UnregisterCommand()); + addCommand("updateAccount", new UpdateAccountCommand()); + addCommand("updateGroup", new UpdateGroupCommand()); + addCommand("verify", new VerifyCommand()); + } + + public static Map getCommands() { + return commands; + } + + private static void addCommand(String name, Command command) { + commands.put(name, command); + } +} diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java new file mode 100644 index 00000000..9b6c0d63 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -0,0 +1,76 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +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.exceptions.DBusException; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.asamk.signal.DbusConfig.SIGNAL_BUSNAME; +import static org.asamk.signal.DbusConfig.SIGNAL_OBJECTPATH; +import static org.asamk.signal.util.ErrorUtils.handleAssertionError; + +public class DaemonCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("--system") + .action(Arguments.storeTrue()) + .help("Use DBus system bus instead of user bus."); + subparser.addArgument("--ignore-attachments") + .help("Don’t download attachments of received messages.") + .action(Arguments.storeTrue()); + subparser.addArgument("--json") + .help("Output received messages in json format, one json object per line.") + .action(Arguments.storeTrue()); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + DBusConnection conn = null; + try { + try { + int busType; + if (ns.getBoolean("system")) { + busType = DBusConnection.SYSTEM; + } else { + busType = DBusConnection.SESSION; + } + conn = DBusConnection.getConnection(busType); + conn.exportObject(SIGNAL_OBJECTPATH, m); + conn.requestBusName(SIGNAL_BUSNAME); + } catch (UnsatisfiedLinkError e) { + System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); + return 1; + } catch (DBusException e) { + e.printStackTrace(); + return 2; + } + boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); + try { + m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, ns.getBoolean("json") ? new JsonDbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH) : new DbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH)); + return 0; + } catch (IOException e) { + System.err.println("Error while receiving messages: " + e.getMessage()); + return 3; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/DbusCommand.java b/src/main/java/org/asamk/signal/commands/DbusCommand.java new file mode 100644 index 00000000..077600e4 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/DbusCommand.java @@ -0,0 +1,9 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import org.asamk.Signal; + +public interface DbusCommand extends Command { + + int handleCommand(Namespace ns, Signal signal); +} diff --git a/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java b/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java new file mode 100644 index 00000000..df47994f --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java @@ -0,0 +1,10 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import org.asamk.Signal; +import org.freedesktop.dbus.DBusConnection; + +public interface ExtendedDbusCommand extends Command { + + int handleCommand(Namespace ns, Signal signal, DBusConnection dbusconnection); +} diff --git a/src/main/java/org/asamk/signal/commands/LinkCommand.java b/src/main/java/org/asamk/signal/commands/LinkCommand.java new file mode 100644 index 00000000..5c7ce403 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/LinkCommand.java @@ -0,0 +1,50 @@ +package org.asamk.signal.commands; + +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.whispersystems.libsignal.InvalidKeyException; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import static org.asamk.signal.util.ErrorUtils.handleAssertionError; + +public class LinkCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-n", "--name") + .help("Specify a name to describe this new device."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager 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()); + } catch (TimeoutException e) { + System.err.println("Link request timed out, please try again."); + return 3; + } catch (IOException e) { + System.err.println("Link request error: " + e.getMessage()); + return 3; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } catch (InvalidKeyException e) { + e.printStackTrace(); + return 2; + } catch (UserAlreadyExists e) { + System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again."); + return 1; + } + return 0; + } +} diff --git a/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java b/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java new file mode 100644 index 00000000..e30acd78 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java @@ -0,0 +1,38 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.util.DateUtils; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; + +import java.io.IOException; +import java.util.List; + +public class ListDevicesCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + List devices = m.getLinkedDevices(); + for (DeviceInfo d : devices) { + System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":"); + System.out.println(" Name: " + d.getName()); + System.out.println(" Created: " + DateUtils.formatTimestamp(d.getCreated())); + System.out.println(" Last seen: " + DateUtils.formatTimestamp(d.getLastSeen())); + } + return 0; + } catch (IOException e) { + e.printStackTrace(); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java new file mode 100644 index 00000000..29d136f5 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -0,0 +1,46 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.storage.groups.GroupInfo; +import org.whispersystems.signalservice.internal.util.Base64; + +import java.util.List; + +public class ListGroupsCommand implements LocalCommand { + + private static void printGroup(GroupInfo group, boolean detailed) { + if (detailed) { + System.out.println(String.format("Id: %s Name: %s Active: %s Members: %s", + Base64.encodeBytes(group.groupId), group.name, group.active, group.members)); + } else { + System.out.println(String.format("Id: %s Name: %s Active: %s", Base64.encodeBytes(group.groupId), + group.name, group.active)); + } + } + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-d", "--detailed").action(Arguments.storeTrue()) + .help("List members of each group"); + subparser.help("List group name and ids"); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + + List groups = m.getGroups(); + boolean detailed = ns.getBoolean("detailed"); + + for (GroupInfo group : groups) { + printGroup(group, detailed); + } + return 0; + } +} diff --git a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java new file mode 100644 index 00000000..b2f6f31c --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java @@ -0,0 +1,47 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; +import org.asamk.signal.util.Hex; +import org.asamk.signal.util.Util; + +import java.util.List; +import java.util.Map; + +public class ListIdentitiesCommand implements LocalCommand { + + private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) { + String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey())); + System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername, + theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits)); + } + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-n", "--number") + .help("Only show identity keys for the given phone number."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + if (ns.get("number") == null) { + for (Map.Entry> keys : m.getIdentities().entrySet()) { + for (JsonIdentityKeyStore.Identity id : keys.getValue()) { + printIdentityFingerprint(m, keys.getKey(), id); + } + } + } else { + String number = ns.getString("number"); + for (JsonIdentityKeyStore.Identity id : m.getIdentities(number)) { + printIdentityFingerprint(m, number, id); + } + } + return 0; + } +} diff --git a/src/main/java/org/asamk/signal/commands/LocalCommand.java b/src/main/java/org/asamk/signal/commands/LocalCommand.java new file mode 100644 index 00000000..9f785802 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/LocalCommand.java @@ -0,0 +1,9 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import org.asamk.signal.manager.Manager; + +public interface LocalCommand extends Command { + + int handleCommand(Namespace ns, Manager m); +} diff --git a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java new file mode 100644 index 00000000..1bde1120 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java @@ -0,0 +1,55 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.GroupIdFormatException; +import org.asamk.signal.GroupNotFoundException; +import org.asamk.signal.NotAGroupMemberException; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.util.Util; +import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; + +import java.io.IOException; + +import static org.asamk.signal.util.ErrorUtils.*; + +public class QuitGroupCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-g", "--group") + .required(true) + .help("Specify the recipient group ID."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + + try { + m.sendQuitGroupMessage(Util.decodeGroupId(ns.getString("group"))); + return 0; + } catch (IOException e) { + handleIOException(e); + return 3; + } catch (EncapsulatedExceptions e) { + handleEncapsulatedExceptions(e); + return 3; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } catch (GroupNotFoundException e) { + handleGroupNotFoundException(e); + return 1; + } catch (NotAGroupMemberException e) { + handleNotAGroupMemberException(e); + return 1; + } catch (GroupIdFormatException e) { + handleGroupIdFormatException(e); + return 1; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java new file mode 100644 index 00000000..03f3d1b2 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -0,0 +1,110 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.Signal; +import org.asamk.signal.JsonReceiveMessageHandler; +import org.asamk.signal.ReceiveMessageHandler; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.util.DateUtils; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.exceptions.DBusException; +import org.whispersystems.signalservice.internal.util.Base64; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.asamk.signal.util.ErrorUtils.handleAssertionError; + +public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-t", "--timeout") + .type(double.class) + .help("Number of seconds to wait for new messages (negative values disable timeout)"); + subparser.addArgument("--ignore-attachments") + .help("Don’t download attachments of received messages.") + .action(Arguments.storeTrue()); + subparser.addArgument("--json") + .help("Output received messages in json format, one json object per line.") + .action(Arguments.storeTrue()); + } + + public int handleCommand(final Namespace ns, final Signal signal, DBusConnection dbusconnection) { + if (dbusconnection != null) { + try { + dbusconnection.addSigHandler(Signal.MessageReceived.class, new DBusSigHandler() { + @Override + public void handle(Signal.MessageReceived s) { + System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n", + s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()), s.getMessage())); + if (s.getGroupId().length > 0) { + System.out.println("Group info:"); + System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId())); + } + if (s.getAttachments().size() > 0) { + System.out.println("Attachments: "); + for (String attachment : s.getAttachments()) { + System.out.println("- Stored plaintext in: " + attachment); + } + } + System.out.println(); + } + }); + dbusconnection.addSigHandler(Signal.ReceiptReceived.class, new DBusSigHandler() { + @Override + public void handle(Signal.ReceiptReceived s) { + System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n", + s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()))); + } + }); + } 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 + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + double timeout = 5; + if (ns.getDouble("timeout") != null) { + timeout = ns.getDouble("timeout"); + } + boolean returnOnTimeout = true; + if (timeout < 0) { + returnOnTimeout = false; + timeout = 3600; + } + boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); + try { + final Manager.ReceiveMessageHandler handler = ns.getBoolean("json") ? new JsonReceiveMessageHandler(m) : new ReceiveMessageHandler(m); + m.receiveMessages((long) (timeout * 1000), TimeUnit.MILLISECONDS, returnOnTimeout, ignoreAttachments, handler); + return 0; + } catch (IOException e) { + System.err.println("Error while receiving messages: " + e.getMessage()); + return 3; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/RegisterCommand.java b/src/main/java/org/asamk/signal/commands/RegisterCommand.java new file mode 100644 index 00000000..546578b6 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/RegisterCommand.java @@ -0,0 +1,29 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; + +import java.io.IOException; + +public class RegisterCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-v", "--voice") + .help("The verification should be done over voice, not sms.") + .action(Arguments.storeTrue()); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + try { + m.register(ns.getBoolean("voice")); + return 0; + } catch (IOException e) { + System.err.println("Request verify error: " + e.getMessage()); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java b/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java new file mode 100644 index 00000000..9f5787ae --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java @@ -0,0 +1,34 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; + +import java.io.IOException; + +public class RemoveDeviceCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-d", "--deviceId") + .type(int.class) + .required(true) + .help("Specify the device you want to remove. Use listDevices to see the deviceIds."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + int deviceId = ns.getInt("deviceId"); + m.removeLinkedDevices(deviceId); + return 0; + } catch (IOException e) { + e.printStackTrace(); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java new file mode 100644 index 00000000..491c26bd --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java @@ -0,0 +1,30 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.io.IOException; + +public class RemovePinCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + m.setRegistrationLockPin(Optional.absent()); + return 0; + } catch (IOException e) { + System.err.println("Remove pin error: " + e.getMessage()); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java new file mode 100644 index 00000000..308e564c --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -0,0 +1,124 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.Signal; +import org.asamk.signal.AttachmentInvalidException; +import org.asamk.signal.GroupIdFormatException; +import org.asamk.signal.GroupNotFoundException; +import org.asamk.signal.NotAGroupMemberException; +import org.asamk.signal.util.IOUtils; +import org.asamk.signal.util.Util; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import static org.asamk.signal.util.ErrorUtils.*; + +public class SendCommand implements DbusCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-g", "--group") + .help("Specify the recipient group ID."); + subparser.addArgument("recipient") + .help("Specify the recipients' phone number.") + .nargs("*"); + subparser.addArgument("-m", "--message") + .help("Specify the message, if missing standard input is used."); + subparser.addArgument("-a", "--attachment") + .nargs("*") + .help("Add file as attachment"); + subparser.addArgument("-e", "--endsession") + .help("Clear session state and send end session message.") + .action(Arguments.storeTrue()); + } + + @Override + public int handleCommand(final Namespace ns, final Signal signal) { + if (!signal.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + + if (ns.getList("recipient") == null || ns.getList("recipient").size() == 0) { + System.err.println("No recipients given"); + System.err.println("Aborting sending."); + return 1; + } + + if (ns.getBoolean("endsession")) { + try { + signal.sendEndSessionMessage(ns.getList("recipient")); + return 0; + } catch (IOException e) { + handleIOException(e); + return 3; + } catch (EncapsulatedExceptions e) { + handleEncapsulatedExceptions(e); + return 3; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } catch (DBusExecutionException e) { + handleDBusExecutionException(e); + return 1; + } + } + + String messageText = ns.getString("message"); + if (messageText == null) { + try { + messageText = IOUtils.readAll(System.in, Charset.defaultCharset()); + } catch (IOException e) { + System.err.println("Failed to read message from stdin: " + e.getMessage()); + System.err.println("Aborting sending."); + return 1; + } + } + + try { + List attachments = ns.getList("attachment"); + if (attachments == null) { + attachments = new ArrayList<>(); + } + if (ns.getString("group") != null) { + byte[] groupId = Util.decodeGroupId(ns.getString("group")); + signal.sendGroupMessage(messageText, attachments, groupId); + } else { + signal.sendMessage(messageText, attachments, ns.getList("recipient")); + } + return 0; + } catch (IOException e) { + handleIOException(e); + return 3; + } catch (EncapsulatedExceptions e) { + handleEncapsulatedExceptions(e); + return 3; + } catch (AssertionError e) { + handleAssertionError(e); + return 1; + } catch (GroupNotFoundException e) { + handleGroupNotFoundException(e); + return 1; + } catch (NotAGroupMemberException e) { + handleNotAGroupMemberException(e); + return 1; + } catch (AttachmentInvalidException e) { + System.err.println("Failed to add attachment: " + e.getMessage()); + System.err.println("Aborting sending."); + return 1; + } catch (DBusExecutionException e) { + handleDBusExecutionException(e); + return 1; + } catch (GroupIdFormatException e) { + handleGroupIdFormatException(e); + return 1; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/SetPinCommand.java b/src/main/java/org/asamk/signal/commands/SetPinCommand.java new file mode 100644 index 00000000..de4e28ec --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/SetPinCommand.java @@ -0,0 +1,33 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.io.IOException; + +public class SetPinCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("registrationLockPin") + .help("The registration lock PIN, that will be required for new registrations (resets after 7 days of inactivity)"); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + String registrationLockPin = ns.getString("registrationLockPin"); + m.setRegistrationLockPin(Optional.of(registrationLockPin)); + return 0; + } catch (IOException e) { + System.err.println("Set pin error: " + e.getMessage()); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/TrustCommand.java b/src/main/java/org/asamk/signal/commands/TrustCommand.java new file mode 100644 index 00000000..13fb63d4 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/TrustCommand.java @@ -0,0 +1,74 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.util.Hex; + +import java.util.Locale; + +public class TrustCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("number") + .help("Specify the phone number, for which to set the trust.") + .required(true); + MutuallyExclusiveGroup mutTrust = subparser.addMutuallyExclusiveGroup(); + 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."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + String number = ns.getString("number"); + if (ns.getBoolean("trust_all_known_keys")) { + boolean res = m.trustIdentityAllKeys(number); + if (!res) { + System.err.println("Failed to set the trust for this number, make sure the number is correct."); + return 1; + } + } else { + String fingerprint = ns.getString("verified_fingerprint"); + if (fingerprint != null) { + fingerprint = fingerprint.replaceAll(" ", ""); + if (fingerprint.length() == 66) { + byte[] fingerprintBytes; + try { + fingerprintBytes = Hex.toByteArray(fingerprint.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; + } + boolean res = m.trustIdentityVerified(number, fingerprintBytes); + if (!res) { + 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) { + boolean res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint); + if (!res) { + System.err.println("Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct."); + return 1; + } + } else { + System.err.println("Fingerprint 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"); + return 1; + } + } + return 0; + } +} diff --git a/src/main/java/org/asamk/signal/commands/UnregisterCommand.java b/src/main/java/org/asamk/signal/commands/UnregisterCommand.java new file mode 100644 index 00000000..3830abe6 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/UnregisterCommand.java @@ -0,0 +1,30 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; + +import java.io.IOException; + +public class UnregisterCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help("Unregister the current device from the signal server."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + m.unregister(); + return 0; + } catch (IOException e) { + System.err.println("Unregister error: " + e.getMessage()); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java b/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java new file mode 100644 index 00000000..31964721 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java @@ -0,0 +1,30 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; + +import java.io.IOException; + +public class UpdateAccountCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help("Update the account attributes on the signal server."); + } + + @Override + public int handleCommand(final Namespace ns, final Manager m) { + if (!m.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + try { + m.updateAccountAttributes(); + return 0; + } catch (IOException e) { + System.err.println("UpdateAccount error: " + e.getMessage()); + return 3; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java new file mode 100644 index 00000000..8f601a68 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -0,0 +1,88 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.Signal; +import org.asamk.signal.AttachmentInvalidException; +import org.asamk.signal.GroupIdFormatException; +import org.asamk.signal.GroupNotFoundException; +import org.asamk.signal.NotAGroupMemberException; +import org.asamk.signal.util.Util; +import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.signalservice.internal.util.Base64; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.asamk.signal.util.ErrorUtils.*; + +public class UpdateGroupCommand implements DbusCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("-g", "--group") + .help("Specify the recipient group ID."); + subparser.addArgument("-n", "--name") + .help("Specify the new group name."); + subparser.addArgument("-a", "--avatar") + .help("Specify a new group avatar image file"); + subparser.addArgument("-m", "--member") + .nargs("*") + .help("Specify one or more members to add to the group"); + } + + @Override + public int handleCommand(final Namespace ns, final Signal signal) { + if (!signal.isRegistered()) { + System.err.println("User is not registered."); + return 1; + } + + try { + byte[] groupId = null; + if (ns.getString("group") != null) { + groupId = Util.decodeGroupId(ns.getString("group")); + } + if (groupId == null) { + groupId = new byte[0]; + } + String groupName = ns.getString("name"); + if (groupName == null) { + groupName = ""; + } + List groupMembers = ns.getList("member"); + if (groupMembers == null) { + groupMembers = new ArrayList<>(); + } + String groupAvatar = ns.getString("avatar"); + if (groupAvatar == null) { + groupAvatar = ""; + } + byte[] newGroupId = signal.updateGroup(groupId, groupName, groupMembers, groupAvatar); + if (groupId.length != newGroupId.length) { + System.out.println("Creating new group \"" + Base64.encodeBytes(newGroupId) + "\" …"); + } + return 0; + } catch (IOException e) { + handleIOException(e); + return 3; + } catch (AttachmentInvalidException e) { + System.err.println("Failed to add avatar attachment for group\": " + e.getMessage()); + System.err.println("Aborting sending."); + return 1; + } catch (GroupNotFoundException e) { + handleGroupNotFoundException(e); + return 1; + } catch (NotAGroupMemberException e) { + handleNotAGroupMemberException(e); + return 1; + } catch (EncapsulatedExceptions e) { + handleEncapsulatedExceptions(e); + return 3; + } catch (GroupIdFormatException e) { + handleGroupIdFormatException(e); + return 1; + } + } +} diff --git a/src/main/java/org/asamk/signal/commands/VerifyCommand.java b/src/main/java/org/asamk/signal/commands/VerifyCommand.java new file mode 100644 index 00000000..aca0d700 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/VerifyCommand.java @@ -0,0 +1,44 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.Manager; +import org.whispersystems.signalservice.internal.push.LockedException; + +import java.io.IOException; + +public class VerifyCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.addArgument("verificationCode") + .help("The verification code you received via sms or voice call."); + subparser.addArgument("-p", "--pin") + .help("The registration lock PIN, that was set by the user (Optional)"); + } + + @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; + } + try { + String verificationCode = ns.getString("verificationCode"); + String pin = ns.getString("pin"); + m.verifyAccount(verificationCode, pin); + return 0; + } catch (LockedException e) { + System.err.println("Verification failed! This number is locked with a pin. Hours remaining until reset: " + (e.getTimeRemaining() / 1000 / 60 / 60)); + System.err.println("Use '--pin PIN_CODE' to specify the registration lock PIN"); + return 3; + } catch (IOException e) { + System.err.println("Verify error: " + e.getMessage()); + return 3; + } + } +} -- 2.50.1