From: AsamK Date: Fri, 15 Jan 2021 17:28:54 +0000 (+0100) Subject: Allow calling signal-cli without -u flag X-Git-Tag: v0.7.3~18 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/ca86c421ebb1a3930fa309a2d95a4090108c2e62 Allow calling signal-cli without -u flag For daemon command all local users will be exposed as dbus objects If only one local user exists, all other commands will use that user, otherwise a user has to be specified. --- diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index bd51033f..5b1653c0 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -44,6 +44,11 @@ Make sure you have full read/write access to the given directory. Specify your phone number, that will be your identifier. The phone number must include the country calling code, i.e. the number must start with a "+" sign. +This flag must not be given for the `link` command. +It is optional for the `daemon` command. +For all other commands it is only optional if there is exactly one local user in the +config directory. + *--dbus*:: Make request via user dbus. @@ -352,6 +357,8 @@ The path of the manifest.json or a zip file containing the sticker pack you wish === daemon signal-cli can run in daemon mode and provides an experimental dbus interface. +If no `-u` username is given, all local users will be exported as separate dbus +objects under the same bus name. *--system*:: Use DBus system bus instead of user bus. @@ -390,6 +397,11 @@ signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER Trust new key, without having verified it. Only use this if you don't care about security:: signal-cli -u USERNAME trust -a NUMBER +== Exit codes +* *1*: Error is probably caused and fixable by the user +* *2*: Some unexpected error +* *3*: Server or IO error + == Files The password and cryptographic keys are created when registering and stored in the current users home directory, the directory can be changed with *--config*: diff --git a/src/main/java/org/asamk/signal/Cli.java b/src/main/java/org/asamk/signal/Cli.java index a0349a31..4579f3f5 100644 --- a/src/main/java/org/asamk/signal/Cli.java +++ b/src/main/java/org/asamk/signal/Cli.java @@ -8,9 +8,9 @@ import org.asamk.signal.commands.Commands; import org.asamk.signal.commands.DbusCommand; import org.asamk.signal.commands.ExtendedDbusCommand; import org.asamk.signal.commands.LocalCommand; +import org.asamk.signal.commands.MultiLocalCommand; import org.asamk.signal.commands.ProvisioningCommand; import org.asamk.signal.commands.RegistrationCommand; -import org.asamk.signal.dbus.DbusSignalImpl; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotRegisteredException; import org.asamk.signal.manager.ProvisioningManager; @@ -21,10 +21,14 @@ import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import java.io.File; import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; public class Cli { @@ -40,15 +44,16 @@ public class Cli { Command command = getCommand(); if (command == null) { logger.error("Command not implemented!"); - return 2; + return 1; } + String username = ns.getString("username"); + if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { - return initDbusClient(command, ns.getBoolean("dbus_system")); + // If username is null, it will connect to the default object path + return initDbusClient(command, username, ns.getBoolean("dbus_system")); } - final String username = ns.getString("username"); - final File dataPath; String config = ns.getString("config"); if (config != null) { @@ -65,59 +70,150 @@ public class Cli { + " because the required native library dependency is missing: libzkgroup"); } - if (username == null) { - ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT); - return handleCommand(command, pm); + if (command instanceof ProvisioningCommand) { + if (username != null) { + System.err.println("You cannot specify a username (phone number) when linking"); + return 1; + } + + return handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceConfiguration); } - if (command instanceof RegistrationCommand) { - final RegistrationManager manager; - try { - manager = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); - } catch (Throwable e) { - logger.error("Error loading or creating state file: {}", e.getMessage()); + if (username == null) { + List usernames = Manager.getAllLocalUsernames(dataPath); + if (usernames.size() == 0) { + System.err.println("No local users found, you first need to register or link an account"); return 1; } - try (RegistrationManager m = manager) { - return handleCommand(command, m); - } catch (Exception e) { - logger.error("Cleanup failed", e); - return 2; + + if (command instanceof MultiLocalCommand) { + return handleMultiLocalCommand((MultiLocalCommand) command, dataPath, serviceConfiguration, usernames); + } + + if (usernames.size() > 1) { + System.err.println("Multiple users found, you need to specify a username (phone number) with -u"); + return 1; } + + username = usernames.get(0); + } else if (!PhoneNumberFormatter.isValidNumber(username, null)) { + System.err.println("Invalid username (phone number), make sure you include the country code."); + return 1; } - Manager manager; + if (command instanceof RegistrationCommand) { + return handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceConfiguration); + } + + if (!(command instanceof LocalCommand)) { + System.err.println("Command only works via dbus"); + return 1; + } + + return handleLocalCommand((LocalCommand) command, username, dataPath, serviceConfiguration); + } + + private int handleProvisioningCommand( + final ProvisioningCommand command, + final File dataPath, + final SignalServiceConfiguration serviceConfiguration + ) { + ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT); + return command.handleCommand(ns, pm); + } + + private int handleRegistrationCommand( + final RegistrationCommand command, + final String username, + final File dataPath, + final SignalServiceConfiguration serviceConfiguration + ) { + final RegistrationManager manager; try { - manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); - } catch (NotRegisteredException e) { - System.err.println("User is not registered."); - return 0; + manager = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); } catch (Throwable e) { - logger.error("Error loading state file: {}", e.getMessage()); - return 1; + logger.error("Error loading or creating state file: {}", e.getMessage()); + return 2; + } + try (RegistrationManager m = manager) { + return command.handleCommand(ns, m); + } catch (IOException e) { + logger.error("Cleanup failed", e); + return 2; } + } - try (Manager m = manager) { - try { - m.checkAccountState(); - } catch (IOException e) { - logger.error("Error while checking account: {}", e.getMessage()); - return 1; + private int handleLocalCommand( + final LocalCommand command, + final String username, + final File dataPath, + final SignalServiceConfiguration serviceConfiguration + ) { + try (Manager m = loadManager(username, dataPath, serviceConfiguration)) { + if (m == null) { + return 2; } - return handleCommand(command, m); + return command.handleCommand(ns, m); } catch (IOException e) { logger.error("Cleanup failed", e); return 2; } } + private int handleMultiLocalCommand( + final MultiLocalCommand command, + final File dataPath, + final SignalServiceConfiguration serviceConfiguration, + final List usernames + ) { + final List managers = usernames.stream() + .map(u -> loadManager(u, dataPath, serviceConfiguration)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + int result = command.handleCommand(ns, managers); + + for (Manager m : managers) { + try { + m.close(); + } catch (IOException e) { + logger.warn("Cleanup failed", e); + } + } + return result; + } + + private Manager loadManager( + final String username, final File dataPath, final SignalServiceConfiguration serviceConfiguration + ) { + Manager manager; + try { + manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); + } catch (NotRegisteredException e) { + logger.error("User " + username + " is not registered."); + return null; + } catch (Throwable e) { + logger.error("Error loading state file for user " + username + ": {}", e.getMessage()); + return null; + } + + try { + manager.checkAccountState(); + } catch (IOException e) { + logger.error("Error while checking account " + username + ": {}", e.getMessage()); + return null; + } + + return manager; + } + private Command getCommand() { String commandKey = ns.getString("command"); return Commands.getCommand(commandKey); } - private int initDbusClient(final Command command, final boolean systemBus) { + private int initDbusClient(final Command command, final String username, final boolean systemBus) { try { DBusConnection.DBusBusType busType; if (systemBus) { @@ -126,8 +222,8 @@ public class Cli { busType = DBusConnection.DBusBusType.SESSION; } try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) { - Signal ts = dBusConn.getRemoteObject(DbusConfig.SIGNAL_BUSNAME, - DbusConfig.SIGNAL_OBJECTPATH, + Signal ts = dBusConn.getRemoteObject(DbusConfig.getBusname(), + DbusConfig.getObjectPath(username), Signal.class); return handleCommand(command, ts, dBusConn); @@ -149,33 +245,6 @@ public class Cli { } } - private int handleCommand(Command command, ProvisioningManager pm) { - if (command instanceof ProvisioningCommand) { - return ((ProvisioningCommand) command).handleCommand(ns, pm); - } else { - System.err.println("Command only works with a username"); - return 1; - } - } - - private int handleCommand(Command command, RegistrationManager m) { - if (command instanceof RegistrationCommand) { - return ((RegistrationCommand) command).handleCommand(ns, m); - } - return 1; - } - - private int handleCommand(Command command, Manager m) { - if (command instanceof LocalCommand) { - return ((LocalCommand) command).handleCommand(ns, m); - } else if (command instanceof DbusCommand) { - return ((DbusCommand) command).handleCommand(ns, new DbusSignalImpl(m)); - } else { - System.err.println("Command only works via dbus"); - return 1; - } - } - /** * Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist: * - $HOME/.config/signal diff --git a/src/main/java/org/asamk/signal/DbusConfig.java b/src/main/java/org/asamk/signal/DbusConfig.java index c0d23175..eb457c39 100644 --- a/src/main/java/org/asamk/signal/DbusConfig.java +++ b/src/main/java/org/asamk/signal/DbusConfig.java @@ -2,6 +2,22 @@ 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"; + private static final String SIGNAL_BUSNAME = "org.asamk.Signal"; + private static final String SIGNAL_OBJECT_BASE_PATH = "/org/asamk/Signal"; + + public static String getBusname() { + return SIGNAL_BUSNAME; + } + + public static String getObjectPath() { + return getObjectPath(null); + } + + public static String getObjectPath(String username) { + if (username == null) { + return SIGNAL_OBJECT_BASE_PATH; + } + + return SIGNAL_OBJECT_BASE_PATH + "/" + username.replace('+', '_'); + } } diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index a8fd3e2b..ddcf47e1 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -30,7 +30,6 @@ import org.asamk.signal.commands.Commands; import org.asamk.signal.manager.LibSignalLogger; import org.asamk.signal.util.SecurityProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import java.security.Security; import java.util.Map; @@ -57,7 +56,7 @@ public class Main { Namespace ns = parseArgs(args); if (ns == null) { - System.exit(1); + System.exit(2); } int res = new Cli(ns).init(); @@ -94,24 +93,6 @@ public class Main { 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"), null)) { - 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 at the same time"); System.exit(2); @@ -152,8 +133,9 @@ public class Main { parser.addArgument("--config") .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); + parser.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification."); + MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup(); - mut.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification."); mut.addArgument("--dbus").help("Make request via user dbus.").action(Arguments.storeTrue()); mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue()); diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 7ebecea6..53f45ca3 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.DbusConfig; import org.asamk.signal.DbusReceiveMessageHandler; import org.asamk.signal.JsonDbusReceiveMessageHandler; import org.asamk.signal.dbus.DbusSignalImpl; @@ -14,13 +15,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; 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 { +public class DaemonCommand implements MultiLocalCommand { private final static Logger logger = LoggerFactory.getLogger(ReceiveCommand.class); @@ -46,46 +45,98 @@ public class DaemonCommand implements LocalCommand { logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead."); } - DBusConnection conn = null; - try { + boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); + + DBusConnection.DBusBusType busType; + if (ns.getBoolean("system")) { + busType = DBusConnection.DBusBusType.SYSTEM; + } else { + busType = DBusConnection.DBusBusType.SESSION; + } + + try (DBusConnection conn = DBusConnection.getConnection(busType)) { + String objectPath = DbusConfig.getObjectPath(); + Thread t = run(conn, objectPath, m, ignoreAttachments, inJson); + + conn.requestBusName(DbusConfig.getBusname()); + try { - DBusConnection.DBusBusType busType; - if (ns.getBoolean("system")) { - busType = DBusConnection.DBusBusType.SYSTEM; - } else { - busType = DBusConnection.DBusBusType.SESSION; - } - conn = DBusConnection.getConnection(busType); - conn.exportObject(SIGNAL_OBJECTPATH, new DbusSignalImpl(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; + t.join(); + } catch (InterruptedException ignored) { } - boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); - try { - m.receiveMessages(1, - TimeUnit.HOURS, - false, - ignoreAttachments, - inJson - ? 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; + return 0; + } catch (DBusException | IOException e) { + logger.error("Dbus command failed", e); + return 2; + } + } + + @Override + public int handleCommand(final Namespace ns, final List managers) { + boolean inJson = ns.getString("output").equals("json") || ns.getBoolean("json"); + + // TODO delete later when "json" variable is removed + if (ns.getBoolean("json")) { + logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead."); + } + + boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); + + DBusConnection.DBusBusType busType; + if (ns.getBoolean("system")) { + busType = DBusConnection.DBusBusType.SYSTEM; + } else { + busType = DBusConnection.DBusBusType.SESSION; + } + + try (DBusConnection conn = DBusConnection.getConnection(busType)) { + List receiveThreads = new ArrayList<>(); + for (Manager m : managers) { + String objectPath = DbusConfig.getObjectPath(m.getUsername()); + Thread thread = run(conn, objectPath, m, ignoreAttachments, inJson); + receiveThreads.add(thread); } - } finally { - if (conn != null) { - conn.disconnect(); + + conn.requestBusName(DbusConfig.getBusname()); + + for (Thread t : receiveThreads) { + try { + t.join(); + } catch (InterruptedException ignored) { + } } + return 0; + } catch (DBusException | IOException e) { + logger.error("Dbus command failed", e); + return 2; } } + + private Thread run( + DBusConnection conn, String objectPath, Manager m, boolean ignoreAttachments, boolean inJson + ) throws DBusException { + conn.exportObject(objectPath, new DbusSignalImpl(m)); + + final Thread thread = new Thread(() -> { + while (true) { + try { + m.receiveMessages(1, + TimeUnit.HOURS, + false, + ignoreAttachments, + inJson + ? new JsonDbusReceiveMessageHandler(m, conn, objectPath) + : new DbusReceiveMessageHandler(m, conn, objectPath)); + } catch (IOException e) { + logger.warn("Receiving messages failed, retrying", e); + } + } + }); + + logger.info("Exported dbus object: " + objectPath); + + thread.start(); + + return thread; + } } diff --git a/src/main/java/org/asamk/signal/commands/DbusCommand.java b/src/main/java/org/asamk/signal/commands/DbusCommand.java index 4dee75b2..1b3a0268 100644 --- a/src/main/java/org/asamk/signal/commands/DbusCommand.java +++ b/src/main/java/org/asamk/signal/commands/DbusCommand.java @@ -3,8 +3,14 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import org.asamk.Signal; +import org.asamk.signal.dbus.DbusSignalImpl; +import org.asamk.signal.manager.Manager; -public interface DbusCommand extends Command { +public interface DbusCommand extends LocalCommand { int handleCommand(Namespace ns, Signal signal); + + default int handleCommand(final Namespace ns, final Manager m) { + return handleCommand(ns, new DbusSignalImpl(m)); + } } diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index 8b694cb6..f17fb967 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -50,7 +50,7 @@ public class GetUserStatusCommand implements LocalCommand { registered = m.areUsersRegistered(new HashSet<>(ns.getList("number"))); } catch (IOException e) { System.err.println("Unable to check if users are registered"); - return 1; + return 3; } // Output diff --git a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java index c5975b0c..bfe46650 100644 --- a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java @@ -3,7 +3,6 @@ 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.manager.Manager; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; @@ -35,15 +34,15 @@ public class JoinGroupCommand implements LocalCommand { linkUrl = GroupInviteLinkUrl.fromUri(uri); } catch (GroupInviteLinkUrl.InvalidGroupLinkException e) { System.err.println("Group link is invalid: " + e.getMessage()); - return 2; + return 1; } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { System.err.println("Group link was created with an incompatible version: " + e.getMessage()); - return 2; + return 1; } if (linkUrl == null) { System.err.println("Link is not a signal group invitation link"); - return 2; + return 1; } try { @@ -64,16 +63,13 @@ public class JoinGroupCommand implements LocalCommand { } catch (IOException e) { e.printStackTrace(); handleIOException(e); - return 1; - } catch (Signal.Error.AttachmentInvalid e) { - System.err.println("Failed to add avatar attachment for group\": " + e.getMessage()); - return 1; + return 3; } catch (DBusExecutionException e) { System.err.println("Failed to send message: " + e.getMessage()); - return 1; + return 2; } catch (GroupLinkNotActiveException e) { System.err.println("Group link is not valid: " + e.getMessage()); - return 2; + return 1; } } } diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index debc2a42..2a267a9a 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -1,5 +1,10 @@ package org.asamk.signal.commands; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; + import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; @@ -9,11 +14,6 @@ import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.asamk.signal.manager.storage.groups.GroupInfo; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -23,7 +23,8 @@ import java.util.stream.Collectors; public class ListGroupsCommand implements LocalCommand { private static Set resolveMembers(Manager m, Set addresses) { - return addresses.stream().map(m::resolveSignalServiceAddress) + return addresses.stream() + .map(m::resolveSignalServiceAddress) .map(SignalServiceAddress::getLegacyIdentifier) .collect(Collectors.toSet()); } @@ -34,7 +35,7 @@ public class ListGroupsCommand implements LocalCommand { System.out.println(); } catch (IOException e) { System.err.println(e.getMessage()); - return 1; + return 3; } return 0; @@ -65,7 +66,8 @@ public class ListGroupsCommand implements LocalCommand { @Override public void attachToSubparser(final Subparser subparser) { - subparser.addArgument("-d", "--detailed").action(Arguments.storeTrue()) + subparser.addArgument("-d", "--detailed") + .action(Arguments.storeTrue()) .help("List the members and group invite links of each group. If output=json, then this is always set"); subparser.help("List group information including names, ids, active status, blocked status and members"); @@ -114,10 +116,16 @@ public class ListGroupsCommand implements LocalCommand { public Set requestingMembers; public String groupInviteLink; - public JsonGroup(String id, String name, boolean isMember, boolean isBlocked, - Set members, Set pendingMembers, - Set requestingMembers, String groupInviteLink) - { + public JsonGroup( + String id, + String name, + boolean isMember, + boolean isBlocked, + Set members, + Set pendingMembers, + Set requestingMembers, + String groupInviteLink + ) { this.id = id; this.name = name; this.isMember = isMember; diff --git a/src/main/java/org/asamk/signal/commands/MultiLocalCommand.java b/src/main/java/org/asamk/signal/commands/MultiLocalCommand.java new file mode 100644 index 00000000..e8ee8e1d --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/MultiLocalCommand.java @@ -0,0 +1,17 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; + +import org.asamk.signal.manager.Manager; + +import java.util.List; + +public interface MultiLocalCommand extends LocalCommand { + + int handleCommand(Namespace ns, List m); + + @Override + default int handleCommand(final Namespace ns, final Manager m) { + return handleCommand(ns, List.of(m)); + } +} diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java index e2002258..30518cb4 100644 --- a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -141,12 +141,9 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { System.out.println(); } }); - } catch (UnsatisfiedLinkError e) { - System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); - return 1; } catch (DBusException e) { e.printStackTrace(); - return 1; + return 2; } while (true) { try { diff --git a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java index ada9c446..52b111ea 100644 --- a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java @@ -20,7 +20,10 @@ public class RemovePinCommand implements LocalCommand { try { m.setRegistrationLockPin(Optional.absent()); return 0; - } catch (IOException | UnauthenticatedResponseException e) { + } catch (UnauthenticatedResponseException e) { + System.err.println("Remove pin error: " + e.getMessage()); + return 2; + } 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 index a4bdc89b..235fc53c 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -8,6 +8,7 @@ import org.asamk.Signal; import org.asamk.signal.manager.groups.GroupIdFormatException; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; +import org.freedesktop.dbus.errors.UnknownObject; import org.freedesktop.dbus.exceptions.DBusExecutionException; import java.io.IOException; @@ -50,7 +51,7 @@ public class SendCommand implements DbusCommand { return 1; } catch (DBusExecutionException e) { System.err.println("Failed to send message: " + e.getMessage()); - return 1; + return 2; } } @@ -89,7 +90,7 @@ public class SendCommand implements DbusCommand { return 1; } catch (DBusExecutionException e) { System.err.println("Failed to send message: " + e.getMessage()); - return 1; + return 2; } try { @@ -99,9 +100,12 @@ public class SendCommand implements DbusCommand { } catch (AssertionError e) { handleAssertionError(e); return 1; + } catch (UnknownObject e) { + System.err.println("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage()); + return 1; } catch (DBusExecutionException e) { System.err.println("Failed to send message: " + e.getMessage()); - return 1; + return 2; } } } diff --git a/src/main/java/org/asamk/signal/commands/SendContactsCommand.java b/src/main/java/org/asamk/signal/commands/SendContactsCommand.java index aaca283a..f5eacf81 100644 --- a/src/main/java/org/asamk/signal/commands/SendContactsCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendContactsCommand.java @@ -20,7 +20,10 @@ public class SendContactsCommand implements LocalCommand { try { m.sendContacts(); return 0; - } catch (IOException | UntrustedIdentityException e) { + } catch (UntrustedIdentityException e) { + System.err.println("SendContacts error: " + e.getMessage()); + return 2; + } catch (IOException e) { System.err.println("SendContacts error: " + e.getMessage()); return 3; } diff --git a/src/main/java/org/asamk/signal/commands/SetPinCommand.java b/src/main/java/org/asamk/signal/commands/SetPinCommand.java index ac601b3b..dbdc83df 100644 --- a/src/main/java/org/asamk/signal/commands/SetPinCommand.java +++ b/src/main/java/org/asamk/signal/commands/SetPinCommand.java @@ -23,7 +23,10 @@ public class SetPinCommand implements LocalCommand { String registrationLockPin = ns.getString("registrationLockPin"); m.setRegistrationLockPin(Optional.of(registrationLockPin)); return 0; - } catch (IOException | UnauthenticatedResponseException e) { + } catch (UnauthenticatedResponseException e) { + System.err.println("Set pin error: " + e.getMessage()); + return 2; + } catch (IOException e) { System.err.println("Set pin 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 index de8564f0..736190b3 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -69,7 +69,7 @@ public class UpdateGroupCommand implements DbusCommand { return 1; } catch (DBusExecutionException e) { System.err.println("Failed to send message: " + e.getMessage()); - return 1; + return 2; } } } diff --git a/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java b/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java index f9f5d95b..725f91b3 100644 --- a/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java +++ b/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java @@ -29,7 +29,7 @@ public class UploadStickerPackCommand implements LocalCommand { return 3; } catch (StickerPackInvalidException e) { System.err.println("Invalid sticker pack: " + e.getMessage()); - return 3; + return 1; } } } diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index a0f6aa53..09e51445 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -288,6 +288,22 @@ public class Manager implements Closeable { return new Manager(account, pathConfig, serviceConfiguration, userAgent); } + public static List getAllLocalUsernames(File settingsPath) { + PathConfig pathConfig = PathConfig.createDefault(settingsPath); + final File dataPath = pathConfig.getDataPath(); + final File[] files = dataPath.listFiles(); + + if (files == null) { + return List.of(); + } + + return Arrays.stream(files) + .filter(File::isFile) + .map(File::getName) + .filter(file -> PhoneNumberFormatter.isValidNumber(file, null)) + .collect(Collectors.toList()); + } + public void checkAccountState() throws IOException { if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { refreshPreKeys(); diff --git a/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/src/main/java/org/asamk/signal/manager/RegistrationManager.java index b3df9376..d6bf78b1 100644 --- a/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -37,11 +37,12 @@ import org.whispersystems.signalservice.internal.push.LockedException; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.Locale; -public class RegistrationManager implements AutoCloseable { +public class RegistrationManager implements Closeable { private SignalAccount account; private final PathConfig pathConfig; @@ -184,7 +185,7 @@ public class RegistrationManager implements AutoCloseable { } @Override - public void close() throws Exception { + public void close() throws IOException { if (account != null) { account.close(); account = null;