X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/c2ae0c17c256329d0d4a9940e67ff1793fb86f48..2972dd27c11aa745b72fb3c6e0a37c44ac95c3f1:/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 5dcf426a..16df90d8 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -19,15 +19,18 @@ package org.asamk.signal; import net.sourceforge.argparse4j.ArgumentParsers; import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.*; -import org.apache.commons.io.IOUtils; import org.apache.http.util.TextUtils; import org.asamk.Signal; 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.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.*; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; +import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; @@ -37,9 +40,15 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +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.concurrent.TimeoutException; public class Main { @@ -142,6 +151,98 @@ public class Main { System.exit(3); } break; + case "link": + if (dBusConn != null) { + System.err.println("link is not yet implemented via dbus"); + System.exit(1); + } + + // When linking, username is null and we always have to create keys + m.createNewIdentity(); + + 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."); + System.exit(3); + } catch (IOException e) { + System.err.println("Link request error: " + e.getMessage()); + System.exit(3); + } catch (InvalidKeyException e) { + e.printStackTrace(); + System.exit(3); + } catch (UserAlreadyExists e) { + System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again."); + System.exit(3); + } + break; + case "addDevice": + if (dBusConn != null) { + System.err.println("link is not yet implemented via dbus"); + System.exit(1); + } + if (!m.isRegistered()) { + System.err.println("User is not registered."); + System.exit(1); + } + try { + m.addDeviceLink(new URI(ns.getString("uri"))); + } catch (IOException e) { + e.printStackTrace(); + System.exit(3); + } catch (InvalidKeyException e) { + e.printStackTrace(); + System.exit(2); + } catch (URISyntaxException e) { + e.printStackTrace(); + System.exit(2); + } + break; + case "listDevices": + if (dBusConn != null) { + System.err.println("listDevices is not yet implemented via dbus"); + System.exit(1); + } + if (!m.isRegistered()) { + System.err.println("User is not registered."); + System.exit(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: " + d.getCreated()); + System.out.println(" Last seen: " + d.getLastSeen()); + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(3); + } + break; + case "removeDevice": + if (dBusConn != null) { + System.err.println("removeDevice is not yet implemented via dbus"); + System.exit(1); + } + if (!m.isRegistered()) { + System.err.println("User is not registered."); + System.exit(1); + } + try { + int deviceId = ns.getInt("deviceId"); + m.removeLinkedDevices(deviceId); + } catch (IOException e) { + e.printStackTrace(); + System.exit(3); + } + break; case "send": if (dBusConn == null && !m.isRegistered()) { System.err.println("User is not registered."); @@ -164,12 +265,14 @@ public class Main { handleAssertionError(e); } catch (DBusExecutionException e) { handleDBusExecutionException(e); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); } } else { String messageText = ns.getString("message"); if (messageText == null) { try { - messageText = IOUtils.toString(System.in); + messageText = readAll(System.in); } catch (IOException e) { System.err.println("Failed to read message from stdin: " + e.getMessage()); System.err.println("Aborting sending."); @@ -202,6 +305,8 @@ public class Main { System.exit(1); } catch (DBusExecutionException e) { handleDBusExecutionException(e); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); } } @@ -280,6 +385,8 @@ public class Main { handleAssertionError(e); } catch (GroupNotFoundException e) { handleGroupNotFoundException(e); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); } break; @@ -312,6 +419,8 @@ public class Main { handleGroupNotFoundException(e); } catch (EncapsulatedExceptions e) { handleEncapsulatedExceptions(e); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); } break; @@ -397,7 +506,7 @@ public class Main { .help("Show package version.") .action(Arguments.version()); parser.addArgument("--config") - .help("Set the path, where to store the config (Default: $HOME/.config/signal-cli)."); + .help("Set the path, where to store the config (Default: $HOME/.config/signal)."); MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup(); mut.addArgument("-u", "--username") @@ -415,6 +524,23 @@ 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.") @@ -467,7 +593,13 @@ public class Main { try { Namespace ns = parser.parseArgs(args); - if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) { + 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)"); @@ -513,6 +645,18 @@ public class Main { System.err.println("Failed to send message: " + e.getMessage()); } + private static String readAll(InputStream in) throws IOException { + StringWriter output = new StringWriter(); + byte[] buffer = new byte[4096]; + long count = 0; + int n; + while (-1 != (n = System.in.read(buffer))) { + output.write(new String(buffer, 0, n, Charset.defaultCharset())); + count += n; + } + return output.toString(); + } + private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final Manager m; @@ -521,9 +665,10 @@ public class Main { } @Override - public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, GroupInfo group) { + public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) { SignalServiceAddress source = envelope.getSourceAddress(); - System.out.println(String.format("Envelope from: %s (device: %d)", source.getNumber(), envelope.getSourceDevice())); + ContactInfo sourceContact = m.getContact(source.getNumber()); + System.out.println(String.format("Envelope from: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + source.getNumber(), envelope.getSourceDevice())); if (source.getRelay().isPresent()) { System.out.println("Relayed by: " + source.getRelay().get()); } @@ -537,49 +682,52 @@ public class Main { } else { if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); + handleSignalServiceDataMessage(message); + } + if (content.getSyncMessage().isPresent()) { + System.out.println("Received a sync message"); + SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); - System.out.println("Message timestamp: " + message.getTimestamp()); - - if (message.getBody().isPresent()) { - System.out.println("Body: " + message.getBody().get()); + if (syncMessage.getContacts().isPresent()) { + System.out.println("Received sync contacts"); + printAttachment(syncMessage.getContacts().get()); } - if (message.getGroupInfo().isPresent()) { - SignalServiceGroup groupInfo = message.getGroupInfo().get(); - System.out.println("Group info:"); - System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId())); - if (groupInfo.getName().isPresent()) { - System.out.println(" Name: " + groupInfo.getName().get()); - } else if (group != null) { - System.out.println(" Name: " + group.name); - } else { - System.out.println(" Name: "); + if (syncMessage.getGroups().isPresent()) { + System.out.println("Received sync groups"); + printAttachment(syncMessage.getGroups().get()); + } + if (syncMessage.getRead().isPresent()) { + System.out.println("Received sync read messages list"); + for (ReadMessage rm : syncMessage.getRead().get()) { + ContactInfo fromContact = m.getContact(rm.getSender()); + System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender() + " Message timestamp: " + rm.getTimestamp()); } - System.out.println(" Type: " + groupInfo.getType()); - if (groupInfo.getMembers().isPresent()) { - for (String member : groupInfo.getMembers().get()) { - System.out.println(" Member: " + member); - } + } + if (syncMessage.getRequest().isPresent()) { + System.out.println("Received sync request"); + if (syncMessage.getRequest().get().isContactsRequest()) { + System.out.println(" - contacts request"); } - if (groupInfo.getAvatar().isPresent()) { - System.out.println(" Avatar:"); - printAttachment(groupInfo.getAvatar().get()); + if (syncMessage.getRequest().get().isGroupsRequest()) { + System.out.println(" - groups request"); } } - if (message.isEndSession()) { - System.out.println("Is end session"); - } - - if (message.getAttachments().isPresent()) { - System.out.println("Attachments: "); - for (SignalServiceAttachment attachment : message.getAttachments().get()) { - printAttachment(attachment); + if (syncMessage.getSent().isPresent()) { + System.out.println("Received sync sent message"); + final SentTranscriptMessage sentTranscriptMessage = syncMessage.getSent().get(); + String to; + if (sentTranscriptMessage.getDestination().isPresent()) { + String dest = sentTranscriptMessage.getDestination().get(); + ContactInfo destContact = m.getContact(dest); + to = (destContact == null ? "" : "“" + destContact.name + "” ") + dest; + } else { + to = "Unknown"; } + System.out.println("To: " + to + " , Message timestamp: " + sentTranscriptMessage.getTimestamp()); + SignalServiceDataMessage message = sentTranscriptMessage.getMessage(); + handleSignalServiceDataMessage(message); } } - if (content.getSyncMessage().isPresent()) { - SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); - System.out.println("Received sync message"); - } } } else { System.out.println("Unknown message received."); @@ -587,6 +735,49 @@ public class Main { System.out.println(); } + private void handleSignalServiceDataMessage(SignalServiceDataMessage message) { + System.out.println("Message timestamp: " + message.getTimestamp()); + + if (message.getBody().isPresent()) { + System.out.println("Body: " + message.getBody().get()); + } + if (message.getGroupInfo().isPresent()) { + SignalServiceGroup groupInfo = message.getGroupInfo().get(); + System.out.println("Group info:"); + System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId())); + if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) { + System.out.println(" Name: " + groupInfo.getName().get()); + } else { + GroupInfo group = m.getGroup(groupInfo.getGroupId()); + if (group != null) { + System.out.println(" Name: " + group.name); + } else { + System.out.println(" Name: "); + } + } + System.out.println(" Type: " + groupInfo.getType()); + if (groupInfo.getMembers().isPresent()) { + for (String member : groupInfo.getMembers().get()) { + System.out.println(" Member: " + member); + } + } + if (groupInfo.getAvatar().isPresent()) { + System.out.println(" Avatar:"); + printAttachment(groupInfo.getAvatar().get()); + } + } + if (message.isEndSession()) { + System.out.println("Is end session"); + } + + if (message.getAttachments().isPresent()) { + System.out.println("Attachments: "); + for (SignalServiceAttachment attachment : message.getAttachments().get()) { + printAttachment(attachment); + } + } + } + private void printAttachment(SignalServiceAttachment attachment) { System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")"); if (attachment.isPointer()) { @@ -610,8 +801,8 @@ public class Main { } @Override - public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, GroupInfo group) { - super.handleMessage(envelope, content, group); + public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) { + super.handleMessage(envelope, content); if (!envelope.isReceipt() && content != null && content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); @@ -643,17 +834,5 @@ public class Main { } } - private void printAttachment(SignalServiceAttachment attachment) { - 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 + (pointer.getRelay().isPresent() ? " Relay: " + pointer.getRelay().get() : "")); - System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : "")); - File file = m.getAttachmentFile(pointer.getId()); - if (file.exists()) { - System.out.println(" Stored plaintext in: " + file); - } - } - } } }