X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/c0a0f89896c3f685f8016ac853fb93c7cee75863..b2289568efb9f9c0fed3fac019ae082a05cd39f5:/src/main/java/org/asamk/signal/Main.java diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 14eb985a..40e28d25 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -48,6 +48,8 @@ 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.TimeoutException; public class Main { @@ -64,6 +66,11 @@ public class Main { System.exit(1); } + int res = handleCommands(ns); + System.exit(res); + } + + private static int handleCommands(Namespace ns) { final String username = ns.getString("username"); Manager m; Signal ts; @@ -87,8 +94,7 @@ public class Main { if (dBusConn != null) { dBusConn.disconnect(); } - System.exit(3); - return; + return 3; } } else { String settingsPath = ns.getString("config"); @@ -109,8 +115,7 @@ public class Main { m.load(); } catch (Exception e) { System.err.println("Error loading state file \"" + m.getFileName() + "\": " + e.getMessage()); - System.exit(2); - return; + return 2; } } } @@ -119,7 +124,7 @@ public class Main { case "register": if (dBusConn != null) { System.err.println("register is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.userHasKeys()) { m.createNewIdentity(); @@ -128,33 +133,33 @@ public class Main { m.register(ns.getBoolean("voice")); } catch (IOException e) { System.err.println("Request verify error: " + e.getMessage()); - System.exit(3); + return 3; } break; case "verify": if (dBusConn != null) { System.err.println("verify is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.userHasKeys()) { System.err.println("User has no keys, first call register."); - System.exit(1); + return 1; } if (m.isRegistered()) { System.err.println("User registration is already verified"); - System.exit(1); + return 1; } try { m.verifyAccount(ns.getString("verificationCode")); } catch (IOException e) { System.err.println("Verify error: " + e.getMessage()); - System.exit(3); + return 3; } break; case "link": if (dBusConn != null) { System.err.println("link is not yet implemented via dbus"); - System.exit(1); + return 1; } // When linking, username is null and we always have to create keys @@ -170,48 +175,48 @@ public class Main { System.out.println("Associated with: " + m.getUsername()); } catch (TimeoutException e) { System.err.println("Link request timed out, please try again."); - System.exit(3); + return 3; } catch (IOException e) { System.err.println("Link request error: " + e.getMessage()); - System.exit(3); + return 3; } catch (InvalidKeyException e) { e.printStackTrace(); - System.exit(3); + return 2; } catch (UserAlreadyExists e) { System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again."); - System.exit(3); + return 1; } break; case "addDevice": if (dBusConn != null) { System.err.println("link is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } try { m.addDeviceLink(new URI(ns.getString("uri"))); } catch (IOException e) { e.printStackTrace(); - System.exit(3); + return 3; } catch (InvalidKeyException e) { e.printStackTrace(); - System.exit(2); + return 2; } catch (URISyntaxException e) { e.printStackTrace(); - System.exit(2); + return 2; } break; case "listDevices": if (dBusConn != null) { System.err.println("listDevices is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } try { List devices = m.getLinkedDevices(); @@ -223,48 +228,52 @@ public class Main { } } catch (IOException e) { e.printStackTrace(); - System.exit(3); + return 3; } break; case "removeDevice": if (dBusConn != null) { System.err.println("removeDevice is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } try { int deviceId = ns.getInt("deviceId"); m.removeLinkedDevices(deviceId); } catch (IOException e) { e.printStackTrace(); - System.exit(3); + return 3; } break; case "send": if (dBusConn == null && !m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } if (ns.getBoolean("endsession")) { if (ns.getList("recipient") == null) { System.err.println("No recipients given"); System.err.println("Aborting sending."); - System.exit(1); + 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; } } else { String messageText = ns.getString("message"); @@ -274,7 +283,7 @@ public class Main { } catch (IOException e) { System.err.println("Failed to read message from stdin: " + e.getMessage()); System.err.println("Aborting sending."); - System.exit(1); + return 1; } } @@ -291,18 +300,26 @@ public class Main { } } 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."); - System.exit(1); + return 1; } catch (DBusExecutionException e) { handleDBusExecutionException(e); + return 1; } } @@ -330,18 +347,19 @@ public class Main { }); } catch (DBusException e) { e.printStackTrace(); + return 1; } while (true) { try { Thread.sleep(10000); } catch (InterruptedException e) { - System.exit(0); + return 0; } } } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } int timeout = 5; if (ns.getInt("timeout") != null) { @@ -356,42 +374,50 @@ public class Main { m.receiveMessages(timeout, returnOnTimeout, new ReceiveMessageHandler(m)); } catch (IOException e) { System.err.println("Error while receiving messages: " + e.getMessage()); - System.exit(3); + return 3; } catch (AssertionError e) { handleAssertionError(e); + return 1; } break; case "quitGroup": if (dBusConn != null) { System.err.println("quitGroup is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } try { m.sendQuitGroupMessage(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; } break; case "updateGroup": if (dBusConn != null) { System.err.println("updateGroup is not yet implemented via dbus"); - System.exit(1); + return 1; } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } try { @@ -405,25 +431,90 @@ public class Main { } } 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."); - System.exit(1); + return 1; } catch (GroupNotFoundException e) { handleGroupNotFoundException(e); + return 1; + } catch (NotAGroupMemberException e) { + handleNotAGroupMemberException(e); + return 1; } catch (EncapsulatedExceptions e) { handleEncapsulatedExceptions(e); + return 3; } + 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()) { + System.out.println(String.format("%s: %s Added: %s Fingerprint: %s", keys.getKey(), id.trustLevel, id.added, Hex.toStringCondensed(id.getFingerprint()))); + } + } + } else { + String number = ns.getString("number"); + for (JsonIdentityKeyStore.Identity id : m.getIdentities(number)) { + System.out.println(String.format("%s: %s Added: %s Fingerprint: %s", number, id.trustLevel, id.added, Hex.toStringCondensed(id.getFingerprint()))); + } + } + 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."); + 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) { + byte[] fingerprintBytes; + try { + fingerprintBytes = Hex.toByteArray(fingerprint.replaceAll(" ", "").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 { + 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."); - System.exit(1); + return 1; } if (!m.isRegistered()) { System.err.println("User is not registered."); - System.exit(1); + return 1; } DBusConnection conn = null; try { @@ -439,15 +530,16 @@ public class Main { conn.requestBusName(SIGNAL_BUSNAME); } catch (DBusException e) { e.printStackTrace(); - System.exit(3); + return 2; } try { m.receiveMessages(3600, false, new DbusReceiveMessageHandler(m, conn)); } catch (IOException e) { System.err.println("Error while receiving messages: " + e.getMessage()); - System.exit(3); + return 3; } catch (AssertionError e) { handleAssertionError(e); + return 1; } } finally { if (conn != null) { @@ -457,7 +549,7 @@ public class Main { break; } - System.exit(0); + return 0; } finally { if (dBusConn != null) { dBusConn.disconnect(); @@ -468,13 +560,18 @@ public class Main { private static void handleGroupNotFoundException(GroupNotFoundException e) { System.err.println("Failed to send to group: " + e.getMessage()); System.err.println("Aborting sending."); - System.exit(1); } + private static void handleNotAGroupMemberException(NotAGroupMemberException e) { + System.err.println("Failed to send to group: " + e.getMessage()); + System.err.println("Update the group on another device to readd the user to this group."); + System.err.println("Aborting sending."); + } + + private static void handleDBusExecutionException(DBusExecutionException e) { System.err.println("Cannot connect to dbus: " + e.getMessage()); System.err.println("Aborting."); - System.exit(1); } private static byte[] decodeGroupId(String groupId) { @@ -573,6 +670,21 @@ public class Main { .nargs("*") .help("Specify one or more members to add to the group"); + 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(int.class) @@ -617,7 +729,6 @@ public class Main { System.err.println("Failed to send/receive message (Assertion): " + e.getMessage()); e.printStackTrace(); System.err.println("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README"); - System.exit(1); } private static void handleEncapsulatedExceptions(EncapsulatedExceptions e) { @@ -657,7 +768,7 @@ public class Main { } @Override - public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) { + public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) { SignalServiceAddress source = envelope.getSourceAddress(); ContactInfo sourceContact = m.getContact(source.getNumber()); System.out.println(String.format("Envelope from: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + source.getNumber(), envelope.getSourceDevice())); @@ -669,6 +780,16 @@ public class Main { if (envelope.isReceipt()) { System.out.println("Got receipt."); } else if (envelope.isSignalMessage() | envelope.isPreKeySignalMessage()) { + if (exception != null) { + if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) { + org.whispersystems.libsignal.UntrustedIdentityException e = (org.whispersystems.libsignal.UntrustedIdentityException) exception; + System.out.println("The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message."); + System.out.println("Use 'signal-cli -u " + m.getUsername() + " listIdentities -n " + e.getName() + "', verify the key and run 'signal-cli -u " + m.getUsername() + " trust -v \"FINGER_PRINT\" " + e.getName() + "' to mark it as trusted"); + System.out.println("If you don't care about security, use 'signal-cli -u " + m.getUsername() + " trust -a " + e.getName() + "' to trust it without verification"); + } else { + System.out.println("Exception: " + exception.getMessage() + " (" + exception.getClass().getSimpleName() + ")"); + } + } if (content == null) { System.out.println("Failed to decrypt message."); } else { @@ -793,8 +914,8 @@ public class Main { } @Override - public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) { - super.handleMessage(envelope, content); + public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) { + super.handleMessage(envelope, content, exception); if (!envelope.isReceipt() && content != null && content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get();