mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-02 20:40:38 +00:00
DBus listen(number<s>) and daemon \-\-number
implement listen method for DBus listen(number<s>) -> <>:: add \-\-number option to daemon subcommand - permits starting daemon in anonymous mode with zero or more numbers - numbers may be added to daemon with listen() and removed with unlisten() change misleading "dataPath" to `settingsPath` in App.java - settingsPath=~/.local/share/signal-cli, while dataPath=~/.local/share/signal-cli/data only use FileLock when necessary in App.java, and unlock when appropriate update documentation
This commit is contained in:
parent
26594dd0ee
commit
690636b83d
12 changed files with 317 additions and 64 deletions
|
@ -94,6 +94,8 @@ public interface Manager extends Closeable {
|
|||
|
||||
void checkAccountState() throws IOException;
|
||||
|
||||
SignalAccount getAccount();
|
||||
|
||||
Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException;
|
||||
|
||||
void updateAccountAttributes(String deviceName) throws IOException;
|
||||
|
|
|
@ -247,6 +247,11 @@ public class ManagerImpl implements Manager {
|
|||
return account.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalAccount getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkAccountState() throws IOException {
|
||||
if (account.getLastReceiveTimestamp() == 0) {
|
||||
|
|
|
@ -116,8 +116,9 @@ public class RegistrationManager implements Closeable {
|
|||
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
|
||||
}
|
||||
|
||||
public void register(boolean voiceVerification, String captcha) throws IOException {
|
||||
public void register(boolean voiceVerification, String captchaString) throws IOException {
|
||||
final ServiceResponse<RequestVerificationCodeResponse> response;
|
||||
final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", "");
|
||||
if (voiceVerification) {
|
||||
response = accountManager.requestVoiceVerificationCode(getDefaultLocale(),
|
||||
Optional.fromNullable(captcha),
|
||||
|
|
|
@ -67,6 +67,14 @@ listAccounts() -> accountList<as>::
|
|||
|
||||
Exceptions: None
|
||||
|
||||
listen(number<s>) -> <>::
|
||||
* number : Phone number
|
||||
|
||||
Starting checking the Signal servers on behalf of this number, and export a DBus object path for it.
|
||||
Fails if user is not already registered.
|
||||
|
||||
Exceptions: Failure
|
||||
|
||||
register(number<s>, voiceVerification<b>) -> <>::
|
||||
* number : Phone number
|
||||
* voiceVerification : true = use voice verification; false = use SMS verification
|
||||
|
@ -78,6 +86,8 @@ registerWithCaptcha(number<s>, voiceVerification<b>, captcha<s>) -> <>::
|
|||
* voiceVerification : true = use voice verification; false = use SMS verification
|
||||
* captcha : Captcha string
|
||||
|
||||
Captcha strings may be obtained from `https://signalcaptchas.org/registration/generate.html`
|
||||
|
||||
Exceptions: Failure, InvalidNumber, RequiresCaptcha
|
||||
|
||||
verify(number<s>, verificationCode<s>) -> <>::
|
||||
|
|
|
@ -494,13 +494,16 @@ 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.
|
||||
If no `-u` username is given, zero or more local users as specified by the
|
||||
`--number` option will be exported as separate dbus objects under the same bus name.
|
||||
If `--number` is omitted, all local users will be exported.
|
||||
|
||||
*--system*::
|
||||
Use DBus system bus instead of user bus.
|
||||
*--ignore-attachments*::
|
||||
Don’t download attachments of received messages.
|
||||
*--number* [NUMBER [NUMBER ...]]::
|
||||
List of zero or more numbers for anonymous daemon to listen to (default=all)
|
||||
|
||||
== Examples
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ public interface SignalControl extends DBusInterface {
|
|||
|
||||
public String version();
|
||||
|
||||
void listen(String number) throws Error.Failure;
|
||||
|
||||
List<DBusPath> listAccounts();
|
||||
|
||||
interface Error {
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -125,12 +126,12 @@ public class App {
|
|||
return;
|
||||
}
|
||||
|
||||
final File dataPath;
|
||||
final File settingsPath;
|
||||
var config = ns.getString("config");
|
||||
if (config != null) {
|
||||
dataPath = new File(config);
|
||||
settingsPath = new File(config);
|
||||
} else {
|
||||
dataPath = getDefaultDataPath();
|
||||
settingsPath = getDefaultSettingsPath();
|
||||
}
|
||||
|
||||
if (!ServiceConfig.getCapabilities().isGv2()) {
|
||||
|
@ -157,22 +158,24 @@ public class App {
|
|||
throw new UserErrorException("You cannot specify a username (phone number) when linking");
|
||||
}
|
||||
|
||||
handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceEnvironment, outputWriter);
|
||||
handleProvisioningCommand((ProvisioningCommand) command, settingsPath, serviceEnvironment, outputWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof MultiLocalCommand) {
|
||||
List<String> usernames = new ArrayList<>();
|
||||
if (username == null) {
|
||||
//anonymous mode
|
||||
handleMultiLocalCommand((MultiLocalCommand) command, settingsPath, serviceEnvironment, usernames, outputWriter, trustNewIdentity);
|
||||
} else {
|
||||
//single-user mode
|
||||
handleMultiLocalCommand((MultiLocalCommand) command, settingsPath, serviceEnvironment, username, outputWriter, trustNewIdentity);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (username == null) {
|
||||
var usernames = Manager.getAllLocalNumbers(dataPath);
|
||||
|
||||
if (command instanceof MultiLocalCommand) {
|
||||
handleMultiLocalCommand((MultiLocalCommand) command,
|
||||
dataPath,
|
||||
serviceEnvironment,
|
||||
usernames,
|
||||
outputWriter,
|
||||
trustNewIdentity);
|
||||
return;
|
||||
}
|
||||
var usernames = Manager.getAllLocalNumbers(settingsPath);
|
||||
|
||||
if (usernames.size() == 0) {
|
||||
throw new UserErrorException("No local users found, you first need to register or link an account");
|
||||
|
@ -187,7 +190,7 @@ public class App {
|
|||
}
|
||||
|
||||
if (command instanceof RegistrationCommand) {
|
||||
handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceEnvironment);
|
||||
handleRegistrationCommand((RegistrationCommand) command, username, settingsPath, serviceEnvironment);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -197,7 +200,7 @@ public class App {
|
|||
|
||||
handleLocalCommand((LocalCommand) command,
|
||||
username,
|
||||
dataPath,
|
||||
settingsPath,
|
||||
serviceEnvironment,
|
||||
outputWriter,
|
||||
trustNewIdentity);
|
||||
|
@ -205,23 +208,23 @@ public class App {
|
|||
|
||||
private void handleProvisioningCommand(
|
||||
final ProvisioningCommand command,
|
||||
final File dataPath,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final OutputWriter outputWriter
|
||||
) throws CommandException {
|
||||
var pm = ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
var pm = ProvisioningManager.init(settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
command.handleCommand(ns, pm, outputWriter);
|
||||
}
|
||||
|
||||
private void handleRegistrationCommand(
|
||||
final RegistrationCommand command,
|
||||
final String username,
|
||||
final File dataPath,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment
|
||||
) throws CommandException {
|
||||
final RegistrationManager manager;
|
||||
try {
|
||||
manager = RegistrationManager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
manager = RegistrationManager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
} catch (Throwable e) {
|
||||
throw new UnexpectedErrorException("Error loading or creating state file: "
|
||||
+ e.getMessage()
|
||||
|
@ -231,6 +234,7 @@ public class App {
|
|||
}
|
||||
try (var m = manager) {
|
||||
command.handleCommand(ns, m);
|
||||
m.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cleanup failed", e);
|
||||
}
|
||||
|
@ -239,13 +243,14 @@ public class App {
|
|||
private void handleLocalCommand(
|
||||
final LocalCommand command,
|
||||
final String username,
|
||||
final File dataPath,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final OutputWriter outputWriter,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) throws CommandException {
|
||||
try (var m = loadManager(username, dataPath, serviceEnvironment, trustNewIdentity)) {
|
||||
try (var m = loadManager(username, settingsPath, serviceEnvironment, trustNewIdentity)) {
|
||||
command.handleCommand(ns, m, outputWriter);
|
||||
m.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cleanup failed", e);
|
||||
}
|
||||
|
@ -253,32 +258,35 @@ public class App {
|
|||
|
||||
private void handleMultiLocalCommand(
|
||||
final MultiLocalCommand command,
|
||||
final File dataPath,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final List<String> usernames,
|
||||
final OutputWriter outputWriter,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) throws CommandException {
|
||||
final var managers = new ArrayList<Manager>();
|
||||
for (String u : usernames) {
|
||||
try {
|
||||
managers.add(loadManager(u, dataPath, serviceEnvironment, trustNewIdentity));
|
||||
} catch (CommandException e) {
|
||||
logger.warn("Ignoring {}: {}", u, e.getMessage());
|
||||
SignalCreator c = new SignalCreator() {
|
||||
@Override
|
||||
public File getSettingsPath() {
|
||||
return settingsPath;
|
||||
}
|
||||
}
|
||||
|
||||
command.handleCommand(ns, managers, new SignalCreator() {
|
||||
@Override
|
||||
public ServiceEnvironment getServiceEnvironment() {
|
||||
return serviceEnvironment;
|
||||
}
|
||||
@Override
|
||||
public ProvisioningManager getNewProvisioningManager() {
|
||||
return ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
return ProvisioningManager.init(settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationManager getNewRegistrationManager(String username) throws IOException {
|
||||
return RegistrationManager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
return RegistrationManager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
}
|
||||
}, outputWriter);
|
||||
};
|
||||
|
||||
final var managers = new ArrayList<Manager>();
|
||||
command.handleCommand(ns, managers, c, outputWriter, trustNewIdentity);
|
||||
|
||||
for (var m : managers) {
|
||||
try {
|
||||
|
@ -289,17 +297,67 @@ public class App {
|
|||
}
|
||||
}
|
||||
|
||||
private Manager loadManager(
|
||||
private void handleMultiLocalCommand(
|
||||
final MultiLocalCommand command,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final String username,
|
||||
final File dataPath,
|
||||
final OutputWriter outputWriter,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) throws CommandException {
|
||||
|
||||
SignalCreator c = new SignalCreator() {
|
||||
@Override
|
||||
public File getSettingsPath() {
|
||||
return settingsPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceEnvironment getServiceEnvironment() {
|
||||
return serviceEnvironment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProvisioningManager getNewProvisioningManager() {
|
||||
return ProvisioningManager.init(settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationManager getNewRegistrationManager(String username) throws IOException {
|
||||
return RegistrationManager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Manager manager = null;
|
||||
try {
|
||||
manager = loadManager(username, settingsPath, serviceEnvironment, trustNewIdentity);
|
||||
} catch (CommandException e) {
|
||||
logger.warn("Ignoring {}: {}", username, e.getMessage());
|
||||
}
|
||||
|
||||
command.handleCommand(ns, manager, c, outputWriter, trustNewIdentity);
|
||||
|
||||
try {
|
||||
manager.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cleanup failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Manager loadManager(
|
||||
final String username,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) throws CommandException {
|
||||
Manager manager;
|
||||
try {
|
||||
manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity);
|
||||
manager = Manager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity);
|
||||
} catch (NotRegisteredException e) {
|
||||
throw new UserErrorException("User " + username + " is not registered.");
|
||||
} catch (OverlappingFileLockException e) {
|
||||
throw new UserErrorException("User " + username + " is already listening.");
|
||||
} catch (Throwable e) {
|
||||
throw new UnexpectedErrorException("Error loading state file for user "
|
||||
+ username
|
||||
|
@ -313,6 +371,13 @@ public class App {
|
|||
try {
|
||||
manager.checkAccountState();
|
||||
} catch (IOException e) {
|
||||
/* In case account isn't registered on Signal servers, close it locally,
|
||||
* thus removing the FileLock so another daemon can get it.
|
||||
*/
|
||||
try {
|
||||
manager.getAccount().close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
throw new IOErrorException("Error while checking account " + username + ": " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
@ -361,9 +426,9 @@ public class App {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the default data directory to be used by signal-cli.
|
||||
* @return the default settings directory to be used by signal-cli.
|
||||
*/
|
||||
private static File getDefaultDataPath() {
|
||||
private static File getDefaultSettingsPath() {
|
||||
return new File(IOUtils.getDataHomeDir(), "signal-cli");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.App;
|
||||
import org.asamk.signal.DbusConfig;
|
||||
import org.asamk.signal.DbusReceiveMessageHandler;
|
||||
import org.asamk.signal.JsonDbusReceiveMessageHandler;
|
||||
|
@ -13,14 +14,19 @@ import org.asamk.signal.OutputWriter;
|
|||
import org.asamk.signal.PlainTextWriter;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.dbus.DbusSignalControlImpl;
|
||||
import org.asamk.signal.dbus.DbusSignalImpl;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironment;
|
||||
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
|
||||
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -28,6 +34,9 @@ import java.util.concurrent.TimeUnit;
|
|||
public class DaemonCommand implements MultiLocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(DaemonCommand.class);
|
||||
public static DBusConnection.DBusBusType dBusType;
|
||||
public static TrustNewIdentity trustNewIdentity;
|
||||
public static OutputWriter outputWriter;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
|
@ -43,6 +52,8 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
subparser.addArgument("--ignore-attachments")
|
||||
.help("Don’t download attachments of received messages.")
|
||||
.action(Arguments.storeTrue());
|
||||
subparser.addArgument("--number", "--numbers").help("Phone numbers").nargs("*")
|
||||
.help("List of zero or more numbers for anonymous daemon to listen to (default=all)");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,6 +65,12 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
public void handleCommand(
|
||||
final Namespace ns, final Manager m, final OutputWriter outputWriter
|
||||
) throws CommandException {
|
||||
handleCommand(ns, m, null, outputWriter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final Namespace ns, final Manager m, final SignalCreator c, final OutputWriter outputWriter, final TrustNewIdentity trustNewIdentity) throws CommandException {
|
||||
//single-user mode
|
||||
boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
|
||||
|
||||
DBusConnection.DBusBusType busType;
|
||||
|
@ -63,6 +80,12 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
|
||||
this.dBusType = busType;
|
||||
this.trustNewIdentity = trustNewIdentity;
|
||||
this.outputWriter = outputWriter;
|
||||
|
||||
checkMacOS();
|
||||
|
||||
try (var conn = DBusConnection.getConnection(busType)) {
|
||||
var objectPath = DbusConfig.getObjectPath();
|
||||
var t = run(conn, objectPath, m, outputWriter, ignoreAttachments);
|
||||
|
@ -73,7 +96,10 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
t.join();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} catch (DBusException | IOException e) {
|
||||
} catch (DBusException e) {
|
||||
logger.error("Dbus command failed", e);
|
||||
throw new UserErrorException("Dbus command failed, daemon already started on this bus.");
|
||||
} catch (IOException e) {
|
||||
logger.error("Dbus command failed", e);
|
||||
throw new UnexpectedErrorException("Dbus command failed", e);
|
||||
}
|
||||
|
@ -81,8 +107,9 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
|
||||
@Override
|
||||
public void handleCommand(
|
||||
final Namespace ns, final List<Manager> managers, final SignalCreator c, final OutputWriter outputWriter
|
||||
final Namespace ns, final List<Manager> managers, final SignalCreator c, final OutputWriter outputWriter, TrustNewIdentity trustNewIdentity
|
||||
) throws CommandException {
|
||||
//anonymous mode
|
||||
boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
|
||||
|
||||
DBusConnection.DBusBusType busType;
|
||||
|
@ -92,6 +119,12 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
|
||||
this.dBusType = busType;
|
||||
this.trustNewIdentity = trustNewIdentity;
|
||||
this.outputWriter = outputWriter;
|
||||
|
||||
checkMacOS();
|
||||
|
||||
try (var conn = DBusConnection.getConnection(busType)) {
|
||||
final var signalControl = new DbusSignalControlImpl(c, m -> {
|
||||
try {
|
||||
|
@ -104,19 +137,59 @@ public class DaemonCommand implements MultiLocalCommand {
|
|||
}, DbusConfig.getObjectPath());
|
||||
conn.exportObject(signalControl);
|
||||
|
||||
List<String> daemonUsernames = ns.<String>getList("number");
|
||||
File settingsPath = c.getSettingsPath();
|
||||
ServiceEnvironment serviceEnvironment = c.getServiceEnvironment();
|
||||
|
||||
if (daemonUsernames == null) {
|
||||
//--numbers option was not given, so add all local usernames
|
||||
daemonUsernames = Manager.getAllLocalNumbers(settingsPath);
|
||||
if (daemonUsernames.size() == 0) {
|
||||
logger.error("No users are registered yet.");
|
||||
throw new UserErrorException("No users are registered yetTry again with signal-cli daemon --numbers");
|
||||
}
|
||||
}
|
||||
|
||||
for (String u : daemonUsernames) {
|
||||
try {
|
||||
managers.add(App.loadManager(u, settingsPath, serviceEnvironment, trustNewIdentity));
|
||||
} catch (CommandException e) {
|
||||
logger.warn("Ignoring {}: {}", u, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
for (var m : managers) {
|
||||
signalControl.addManager(m);
|
||||
}
|
||||
|
||||
conn.requestBusName(DbusConfig.getBusname());
|
||||
logger.info("Starting daemon.");
|
||||
|
||||
signalControl.run();
|
||||
} catch (DBusException | IOException e) {
|
||||
} catch (DBusException e) {
|
||||
logger.error("Dbus command failed", e);
|
||||
throw new UserErrorException("Dbus command failed, daemon alreadytarted on this bus.");
|
||||
} catch (IOException e ) {
|
||||
logger.error("Dbus command failed", e);
|
||||
throw new UnexpectedErrorException("Dbus command failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMacOS() throws UserErrorException {
|
||||
if (System.getProperty("os.name").toLowerCase().startsWith("mac ")) {
|
||||
String dBusVar = System.getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET");
|
||||
if (dBusVar == null || dBusVar.isBlank()) {
|
||||
String message = "\n\n" +
|
||||
"*************************************" +
|
||||
"\n\nDBUS_LAUNCHD_SESSION_BUS_SOCKET is not set. Issue the command:\n\n" +
|
||||
"export DBUS_LAUNCHD_SESSION_BUS_SOCKET=$(launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET)\n" +
|
||||
"\nand then try again.\n\n" +
|
||||
"*************************************";
|
||||
throw new UserErrorException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Thread run(
|
||||
DBusConnection conn, String objectPath, Manager m, OutputWriter outputWriter, boolean ignoreAttachments
|
||||
) throws DBusException {
|
||||
|
|
|
@ -5,19 +5,12 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
|||
import org.asamk.signal.OutputWriter;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MultiLocalCommand extends LocalCommand {
|
||||
void handleCommand(Namespace ns, List<Manager> m, SignalCreator c, OutputWriter outputWriter, TrustNewIdentity trustNewIdentity) throws CommandException;
|
||||
void handleCommand(Namespace ns, Manager m, SignalCreator c, OutputWriter outputWriter, TrustNewIdentity trustNewIdentity) throws CommandException;
|
||||
|
||||
void handleCommand(
|
||||
Namespace ns, List<Manager> m, SignalCreator c, OutputWriter outputWriter
|
||||
) throws CommandException;
|
||||
|
||||
@Override
|
||||
default void handleCommand(
|
||||
final Namespace ns, final Manager m, final OutputWriter outputWriter
|
||||
) throws CommandException {
|
||||
handleCommand(ns, List.of(m), null, outputWriter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,17 @@ package org.asamk.signal.commands;
|
|||
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.RegistrationManager;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface SignalCreator {
|
||||
|
||||
File getSettingsPath();
|
||||
|
||||
ServiceEnvironment getServiceEnvironment();
|
||||
|
||||
ProvisioningManager getNewProvisioningManager();
|
||||
|
||||
RegistrationManager getNewRegistrationManager(String username) throws IOException;
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.asamk.signal.manager.groups.GroupPermission;
|
|||
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
|
||||
import org.asamk.signal.manager.groups.LastGroupAdminException;
|
||||
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.storage.recipients.Contact;
|
||||
import org.asamk.signal.manager.storage.recipients.Profile;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||
|
@ -80,6 +81,11 @@ public class DbusManagerImpl implements Manager {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalAccount getAccount() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Pair<String, UUID>> areUsersRegistered(final Set<String> numbers) throws IOException {
|
||||
final var numbersList = new ArrayList<>(numbers);
|
||||
|
|
|
@ -1,36 +1,65 @@
|
|||
package org.asamk.signal.dbus;
|
||||
|
||||
import org.asamk.SignalControl;
|
||||
import org.asamk.SignalControl.Error;
|
||||
import org.asamk.signal.App;
|
||||
import org.asamk.signal.BaseConfig;
|
||||
import org.asamk.signal.DbusConfig;
|
||||
import org.asamk.signal.DbusReceiveMessageHandler;
|
||||
import org.asamk.signal.JsonDbusReceiveMessageHandler;
|
||||
import org.asamk.signal.JsonWriter;
|
||||
import org.asamk.signal.OutputWriter;
|
||||
import org.asamk.signal.PlainTextWriter;
|
||||
import org.asamk.signal.commands.DaemonCommand;
|
||||
import org.asamk.signal.commands.SignalCreator;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.RegistrationManager;
|
||||
import org.asamk.signal.manager.UserAlreadyExists;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironment;
|
||||
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
|
||||
|
||||
import org.freedesktop.dbus.DBusPath;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
||||
|
||||
private final SignalCreator c;
|
||||
private final Function<Manager, Thread> newManagerRunner;
|
||||
private static SignalCreator c;
|
||||
private static Function<Manager, Thread> newManagerRunner;
|
||||
|
||||
private final List<Pair<Manager, Thread>> receiveThreads = new ArrayList<>();
|
||||
private final Object stopTrigger = new Object();
|
||||
private final String objectPath;
|
||||
private static List<Pair<Manager, Thread>> receiveThreads = new ArrayList<>();
|
||||
private static Object stopTrigger = new Object();
|
||||
private static String objectPath;
|
||||
private static DBusConnection.DBusBusType busType;
|
||||
public static RegistrationManager registrationManager;
|
||||
public static ProvisioningManager provisioningManager;
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(DbusSignalControlImpl.class);
|
||||
|
||||
public DbusSignalControlImpl(
|
||||
final SignalCreator c, final Function<Manager, Thread> newManagerRunner, final String objectPath
|
||||
|
@ -38,9 +67,10 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
|||
this.c = c;
|
||||
this.newManagerRunner = newManagerRunner;
|
||||
this.objectPath = objectPath;
|
||||
this.busType = busType;
|
||||
}
|
||||
|
||||
public void addManager(Manager m) {
|
||||
public static void addManager(Manager m) {
|
||||
var thread = newManagerRunner.apply(m);
|
||||
if (thread == null) {
|
||||
return;
|
||||
|
@ -125,7 +155,9 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
|||
) throws Error.Failure, Error.InvalidNumber {
|
||||
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
|
||||
final Manager manager = registrationManager.verifyAccount(verificationCode, pin);
|
||||
logger.info("Registration of " + number + " verified");
|
||||
addManager(manager);
|
||||
registrationManager.close();
|
||||
} catch (IOException | KeyBackupSystemNoDataException | KeyBackupServicePinException e) {
|
||||
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage());
|
||||
}
|
||||
|
@ -139,9 +171,19 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
|||
new Thread(() -> {
|
||||
try {
|
||||
final Manager manager = provisioningManager.finishDeviceLink(newDeviceName);
|
||||
logger.info("Linking of " + newDeviceName + " successful");
|
||||
addManager(manager);
|
||||
} catch (IOException | TimeoutException | UserAlreadyExists e) {
|
||||
e.printStackTrace();
|
||||
//no need to close provisioningManager; it cleaned up during finishDeviceLink
|
||||
} catch (TimeoutException e) {
|
||||
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + ": Link request timed out, please try again.");
|
||||
} catch (IOException e) {
|
||||
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + ": Link request error: " + e.getMessage());
|
||||
} catch (UserAlreadyExists e) {
|
||||
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + ": The user "
|
||||
+ e.getNumber()
|
||||
+ " already exists\nDelete \""
|
||||
+ e.getFileName()
|
||||
+ "\" before trying again.");
|
||||
}
|
||||
}).start();
|
||||
return deviceLinkUri.toString();
|
||||
|
@ -155,6 +197,51 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
|||
return BaseConfig.PROJECT_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen(String number) {
|
||||
try {
|
||||
File settingsPath = c.getSettingsPath();
|
||||
List<String> usernames = Manager.getAllLocalNumbers(settingsPath);
|
||||
if (!usernames.contains(number)) {
|
||||
throw new Error.Failure("Listen: " + number + " is not registered.");
|
||||
}
|
||||
String objectPath = DbusConfig.getObjectPath(number);
|
||||
DBusConnection.DBusBusType busType = DaemonCommand.dBusType;
|
||||
ServiceEnvironment serviceEnvironment = c.getServiceEnvironment();
|
||||
TrustNewIdentity trustNewIdentity = DaemonCommand.trustNewIdentity;
|
||||
|
||||
//create new manager for this number
|
||||
final Manager m = App.loadManager(number, settingsPath, serviceEnvironment, trustNewIdentity);
|
||||
addManager(m);
|
||||
final var thread = new Thread(() -> {
|
||||
try {
|
||||
OutputWriter outputWriter = DaemonCommand.outputWriter;
|
||||
boolean ignoreAttachments = false;
|
||||
DBusConnection conn = DBusConnection.getConnection(busType);
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
final var receiveMessageHandler = outputWriter instanceof JsonWriter
|
||||
? new JsonDbusReceiveMessageHandler(m, (JsonWriter) outputWriter, conn, objectPath)
|
||||
: new DbusReceiveMessageHandler(m, (PlainTextWriter) outputWriter, conn, objectPath);
|
||||
m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, receiveMessageHandler);
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
logger.warn("Receiving messages failed, retrying", e);
|
||||
}
|
||||
}
|
||||
} catch (DBusException e) {
|
||||
throw new Error.Failure(e.getClass().getSimpleName() + " Listen error: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (OverlappingFileLockException e) {
|
||||
logger.warn("Ignoring {}: {}", number, e.getMessage());
|
||||
throw new Error.Failure(e.getClass().getSimpleName() + " Already listening: " + e.getMessage());
|
||||
} catch (CommandException e) {
|
||||
logger.warn("Ignoring {}: {}", number, e.getMessage());
|
||||
throw new Error.Failure(e.getClass().getSimpleName() + " Listen error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DBusPath> listAccounts() {
|
||||
synchronized (receiveThreads) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue