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.
This commit is contained in:
AsamK 2021-01-15 18:28:54 +01:00
parent a97bbf8608
commit ca86c421eb
19 changed files with 356 additions and 172 deletions

View file

@ -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. 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. 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*:: *--dbus*::
Make request via user 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 === daemon
signal-cli can run in daemon mode and provides an experimental dbus interface. 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*:: *--system*::
Use DBus system bus instead of user bus. 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:: Trust new key, without having verified it. Only use this if you don't care about security::
signal-cli -u USERNAME trust -a NUMBER signal-cli -u USERNAME trust -a NUMBER
== Exit codes
* *1*: Error is probably caused and fixable by the user
* *2*: Some unexpected error
* *3*: Server or IO error
== Files == 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*: The password and cryptographic keys are created when registering and stored in the current users home directory, the directory can be changed with *--config*:

View file

@ -8,9 +8,9 @@ import org.asamk.signal.commands.Commands;
import org.asamk.signal.commands.DbusCommand; import org.asamk.signal.commands.DbusCommand;
import org.asamk.signal.commands.ExtendedDbusCommand; import org.asamk.signal.commands.ExtendedDbusCommand;
import org.asamk.signal.commands.LocalCommand; import org.asamk.signal.commands.LocalCommand;
import org.asamk.signal.commands.MultiLocalCommand;
import org.asamk.signal.commands.ProvisioningCommand; import org.asamk.signal.commands.ProvisioningCommand;
import org.asamk.signal.commands.RegistrationCommand; import org.asamk.signal.commands.RegistrationCommand;
import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotRegisteredException; import org.asamk.signal.manager.NotRegisteredException;
import org.asamk.signal.manager.ProvisioningManager; 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.freedesktop.dbus.exceptions.DBusException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class Cli { public class Cli {
@ -40,15 +44,16 @@ public class Cli {
Command command = getCommand(); Command command = getCommand();
if (command == null) { if (command == null) {
logger.error("Command not implemented!"); logger.error("Command not implemented!");
return 2; return 1;
} }
String username = ns.getString("username");
if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { 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; final File dataPath;
String config = ns.getString("config"); String config = ns.getString("config");
if (config != null) { if (config != null) {
@ -65,59 +70,150 @@ public class Cli {
+ " because the required native library dependency is missing: libzkgroup"); + " because the required native library dependency is missing: libzkgroup");
} }
if (username == null) { if (command instanceof ProvisioningCommand) {
ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT); if (username != null) {
return handleCommand(command, pm); System.err.println("You cannot specify a username (phone number) when linking");
}
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());
return 1; return 1;
} }
try (RegistrationManager m = manager) {
return handleCommand(command, m); return handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceConfiguration);
} catch (Exception e) {
logger.error("Cleanup failed", e);
return 2;
}
} }
Manager manager; if (username == null) {
try { List<String> usernames = Manager.getAllLocalUsernames(dataPath);
manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); if (usernames.size() == 0) {
} catch (NotRegisteredException e) { System.err.println("No local users found, you first need to register or link an account");
System.err.println("User is not registered."); return 1;
return 0; }
} catch (Throwable e) {
logger.error("Error loading state file: {}", e.getMessage()); 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; return 1;
} }
try (Manager m = manager) { if (command instanceof RegistrationCommand) {
try { return handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceConfiguration);
m.checkAccountState(); }
} catch (IOException e) {
logger.error("Error while checking account: {}", e.getMessage());
return 1;
}
return handleCommand(command, m); 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 = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
} catch (Throwable e) {
logger.error("Error loading or creating state file: {}", e.getMessage());
return 2;
}
try (RegistrationManager m = manager) {
return command.handleCommand(ns, m);
} catch (IOException e) { } catch (IOException e) {
logger.error("Cleanup failed", e); logger.error("Cleanup failed", e);
return 2; return 2;
} }
} }
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 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<String> usernames
) {
final List<Manager> 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() { private Command getCommand() {
String commandKey = ns.getString("command"); String commandKey = ns.getString("command");
return Commands.getCommand(commandKey); 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 { try {
DBusConnection.DBusBusType busType; DBusConnection.DBusBusType busType;
if (systemBus) { if (systemBus) {
@ -126,8 +222,8 @@ public class Cli {
busType = DBusConnection.DBusBusType.SESSION; busType = DBusConnection.DBusBusType.SESSION;
} }
try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) { try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) {
Signal ts = dBusConn.getRemoteObject(DbusConfig.SIGNAL_BUSNAME, Signal ts = dBusConn.getRemoteObject(DbusConfig.getBusname(),
DbusConfig.SIGNAL_OBJECTPATH, DbusConfig.getObjectPath(username),
Signal.class); Signal.class);
return handleCommand(command, ts, dBusConn); 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: * Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
* - $HOME/.config/signal * - $HOME/.config/signal

View file

@ -2,6 +2,22 @@ package org.asamk.signal;
public class DbusConfig { public class DbusConfig {
public static final String SIGNAL_BUSNAME = "org.asamk.Signal"; private static final String SIGNAL_BUSNAME = "org.asamk.Signal";
public static final String SIGNAL_OBJECTPATH = "/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('+', '_');
}
} }

View file

@ -30,7 +30,6 @@ import org.asamk.signal.commands.Commands;
import org.asamk.signal.manager.LibSignalLogger; import org.asamk.signal.manager.LibSignalLogger;
import org.asamk.signal.util.SecurityProvider; import org.asamk.signal.util.SecurityProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.security.Security; import java.security.Security;
import java.util.Map; import java.util.Map;
@ -57,7 +56,7 @@ public class Main {
Namespace ns = parseArgs(args); Namespace ns = parseArgs(args);
if (ns == null) { if (ns == null) {
System.exit(1); System.exit(2);
} }
int res = new Cli(ns).init(); int res = new Cli(ns).init();
@ -94,24 +93,6 @@ public class Main {
return null; 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) { 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.err.println("You cannot specify recipients by phone number and groups at the same time");
System.exit(2); System.exit(2);
@ -152,8 +133,9 @@ public class Main {
parser.addArgument("--config") parser.addArgument("--config")
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); .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(); 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").help("Make request via user dbus.").action(Arguments.storeTrue());
mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue()); mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue());

View file

@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.DbusConfig;
import org.asamk.signal.DbusReceiveMessageHandler; import org.asamk.signal.DbusReceiveMessageHandler;
import org.asamk.signal.JsonDbusReceiveMessageHandler; import org.asamk.signal.JsonDbusReceiveMessageHandler;
import org.asamk.signal.dbus.DbusSignalImpl; import org.asamk.signal.dbus.DbusSignalImpl;
@ -14,13 +15,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.asamk.signal.DbusConfig.SIGNAL_BUSNAME; public class DaemonCommand implements MultiLocalCommand {
import static org.asamk.signal.DbusConfig.SIGNAL_OBJECTPATH;
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
public class DaemonCommand implements LocalCommand {
private final static Logger logger = LoggerFactory.getLogger(ReceiveCommand.class); 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."); logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
} }
DBusConnection conn = null; boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
try {
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 { try {
DBusConnection.DBusBusType busType; t.join();
if (ns.getBoolean("system")) { } catch (InterruptedException ignored) {
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;
}
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;
}
} finally {
if (conn != null) {
conn.disconnect();
} }
return 0;
} catch (DBusException | IOException e) {
logger.error("Dbus command failed", e);
return 2;
} }
} }
@Override
public int handleCommand(final Namespace ns, final List<Manager> 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<Thread> receiveThreads = new ArrayList<>();
for (Manager m : managers) {
String objectPath = DbusConfig.getObjectPath(m.getUsername());
Thread thread = run(conn, objectPath, m, ignoreAttachments, inJson);
receiveThreads.add(thread);
}
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;
}
} }

View file

@ -3,8 +3,14 @@ package org.asamk.signal.commands;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import org.asamk.Signal; 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); int handleCommand(Namespace ns, Signal signal);
default int handleCommand(final Namespace ns, final Manager m) {
return handleCommand(ns, new DbusSignalImpl(m));
}
} }

View file

@ -50,7 +50,7 @@ public class GetUserStatusCommand implements LocalCommand {
registered = m.areUsersRegistered(new HashSet<>(ns.getList("number"))); registered = m.areUsersRegistered(new HashSet<>(ns.getList("number")));
} catch (IOException e) { } catch (IOException e) {
System.err.println("Unable to check if users are registered"); System.err.println("Unable to check if users are registered");
return 1; return 3;
} }
// Output // Output

View file

@ -3,7 +3,6 @@ package org.asamk.signal.commands;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.Signal;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
@ -35,15 +34,15 @@ public class JoinGroupCommand implements LocalCommand {
linkUrl = GroupInviteLinkUrl.fromUri(uri); linkUrl = GroupInviteLinkUrl.fromUri(uri);
} catch (GroupInviteLinkUrl.InvalidGroupLinkException e) { } catch (GroupInviteLinkUrl.InvalidGroupLinkException e) {
System.err.println("Group link is invalid: " + e.getMessage()); System.err.println("Group link is invalid: " + e.getMessage());
return 2; return 1;
} catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
System.err.println("Group link was created with an incompatible version: " + e.getMessage()); System.err.println("Group link was created with an incompatible version: " + e.getMessage());
return 2; return 1;
} }
if (linkUrl == null) { if (linkUrl == null) {
System.err.println("Link is not a signal group invitation link"); System.err.println("Link is not a signal group invitation link");
return 2; return 1;
} }
try { try {
@ -64,16 +63,13 @@ public class JoinGroupCommand implements LocalCommand {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
handleIOException(e); handleIOException(e);
return 1; return 3;
} catch (Signal.Error.AttachmentInvalid e) {
System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
return 1;
} catch (DBusExecutionException e) { } catch (DBusExecutionException e) {
System.err.println("Failed to send message: " + e.getMessage()); System.err.println("Failed to send message: " + e.getMessage());
return 1; return 2;
} catch (GroupLinkNotActiveException e) { } catch (GroupLinkNotActiveException e) {
System.err.println("Group link is not valid: " + e.getMessage()); System.err.println("Group link is not valid: " + e.getMessage());
return 2; return 1;
} }
} }
} }

View file

@ -1,5 +1,10 @@
package org.asamk.signal.commands; 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.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; 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.asamk.signal.manager.storage.groups.GroupInfo;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -23,7 +23,8 @@ import java.util.stream.Collectors;
public class ListGroupsCommand implements LocalCommand { public class ListGroupsCommand implements LocalCommand {
private static Set<String> resolveMembers(Manager m, Set<SignalServiceAddress> addresses) { private static Set<String> resolveMembers(Manager m, Set<SignalServiceAddress> addresses) {
return addresses.stream().map(m::resolveSignalServiceAddress) return addresses.stream()
.map(m::resolveSignalServiceAddress)
.map(SignalServiceAddress::getLegacyIdentifier) .map(SignalServiceAddress::getLegacyIdentifier)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@ -34,7 +35,7 @@ public class ListGroupsCommand implements LocalCommand {
System.out.println(); System.out.println();
} catch (IOException e) { } catch (IOException e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
return 1; return 3;
} }
return 0; return 0;
@ -65,7 +66,8 @@ public class ListGroupsCommand implements LocalCommand {
@Override @Override
public void attachToSubparser(final Subparser subparser) { 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"); .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"); 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<String> requestingMembers; public Set<String> requestingMembers;
public String groupInviteLink; public String groupInviteLink;
public JsonGroup(String id, String name, boolean isMember, boolean isBlocked, public JsonGroup(
Set<String> members, Set<String> pendingMembers, String id,
Set<String> requestingMembers, String groupInviteLink) String name,
{ boolean isMember,
boolean isBlocked,
Set<String> members,
Set<String> pendingMembers,
Set<String> requestingMembers,
String groupInviteLink
) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.isMember = isMember; this.isMember = isMember;

View file

@ -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<Manager> m);
@Override
default int handleCommand(final Namespace ns, final Manager m) {
return handleCommand(ns, List.of(m));
}
}

View file

@ -141,12 +141,9 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
System.out.println(); System.out.println();
} }
}); });
} catch (UnsatisfiedLinkError e) {
System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
return 1;
} catch (DBusException e) { } catch (DBusException e) {
e.printStackTrace(); e.printStackTrace();
return 1; return 2;
} }
while (true) { while (true) {
try { try {

View file

@ -20,7 +20,10 @@ public class RemovePinCommand implements LocalCommand {
try { try {
m.setRegistrationLockPin(Optional.absent()); m.setRegistrationLockPin(Optional.absent());
return 0; 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()); System.err.println("Remove pin error: " + e.getMessage());
return 3; return 3;
} }

View file

@ -8,6 +8,7 @@ import org.asamk.Signal;
import org.asamk.signal.manager.groups.GroupIdFormatException; import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.freedesktop.dbus.errors.UnknownObject;
import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.exceptions.DBusExecutionException;
import java.io.IOException; import java.io.IOException;
@ -50,7 +51,7 @@ public class SendCommand implements DbusCommand {
return 1; return 1;
} catch (DBusExecutionException e) { } catch (DBusExecutionException e) {
System.err.println("Failed to send message: " + e.getMessage()); System.err.println("Failed to send message: " + e.getMessage());
return 1; return 2;
} }
} }
@ -89,7 +90,7 @@ public class SendCommand implements DbusCommand {
return 1; return 1;
} catch (DBusExecutionException e) { } catch (DBusExecutionException e) {
System.err.println("Failed to send message: " + e.getMessage()); System.err.println("Failed to send message: " + e.getMessage());
return 1; return 2;
} }
try { try {
@ -99,9 +100,12 @@ public class SendCommand implements DbusCommand {
} catch (AssertionError e) { } catch (AssertionError e) {
handleAssertionError(e); handleAssertionError(e);
return 1; 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) { } catch (DBusExecutionException e) {
System.err.println("Failed to send message: " + e.getMessage()); System.err.println("Failed to send message: " + e.getMessage());
return 1; return 2;
} }
} }
} }

View file

@ -20,7 +20,10 @@ public class SendContactsCommand implements LocalCommand {
try { try {
m.sendContacts(); m.sendContacts();
return 0; 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()); System.err.println("SendContacts error: " + e.getMessage());
return 3; return 3;
} }

View file

@ -23,7 +23,10 @@ public class SetPinCommand implements LocalCommand {
String registrationLockPin = ns.getString("registrationLockPin"); String registrationLockPin = ns.getString("registrationLockPin");
m.setRegistrationLockPin(Optional.of(registrationLockPin)); m.setRegistrationLockPin(Optional.of(registrationLockPin));
return 0; 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()); System.err.println("Set pin error: " + e.getMessage());
return 3; return 3;
} }

View file

@ -69,7 +69,7 @@ public class UpdateGroupCommand implements DbusCommand {
return 1; return 1;
} catch (DBusExecutionException e) { } catch (DBusExecutionException e) {
System.err.println("Failed to send message: " + e.getMessage()); System.err.println("Failed to send message: " + e.getMessage());
return 1; return 2;
} }
} }
} }

View file

@ -29,7 +29,7 @@ public class UploadStickerPackCommand implements LocalCommand {
return 3; return 3;
} catch (StickerPackInvalidException e) { } catch (StickerPackInvalidException e) {
System.err.println("Invalid sticker pack: " + e.getMessage()); System.err.println("Invalid sticker pack: " + e.getMessage());
return 3; return 1;
} }
} }
} }

View file

@ -288,6 +288,22 @@ public class Manager implements Closeable {
return new Manager(account, pathConfig, serviceConfiguration, userAgent); return new Manager(account, pathConfig, serviceConfiguration, userAgent);
} }
public static List<String> 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 { public void checkAccountState() throws IOException {
if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
refreshPreKeys(); refreshPreKeys();

View file

@ -37,11 +37,12 @@ import org.whispersystems.signalservice.internal.push.LockedException;
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
public class RegistrationManager implements AutoCloseable { public class RegistrationManager implements Closeable {
private SignalAccount account; private SignalAccount account;
private final PathConfig pathConfig; private final PathConfig pathConfig;
@ -184,7 +185,7 @@ public class RegistrationManager implements AutoCloseable {
} }
@Override @Override
public void close() throws Exception { public void close() throws IOException {
if (account != null) { if (account != null) {
account.close(); account.close();
account = null; account = null;