modify DBus unlisten and unregister

add unlisten() and unregister() to this branch

move unlisten() and unregister() methods from Signal.java to SignalControl.java for consistency

update documentation
This commit is contained in:
John Freed 2021-10-06 03:20:29 +02:00
parent 690636b83d
commit 3e67d61681
9 changed files with 196 additions and 14 deletions

View file

@ -922,8 +922,12 @@ public class ManagerImpl implements Manager {
while (!Thread.interrupted()) { while (!Thread.interrupted()) {
SignalServiceEnvelope envelope; SignalServiceEnvelope envelope;
final CachedMessage[] cachedMessage = {null}; final CachedMessage[] cachedMessage = {null};
account.setLastReceiveTimestamp(System.currentTimeMillis()); if (account == null) {
logger.debug("Account closed.");
break;
}
logger.debug("Checking for new message from server"); logger.debug("Checking for new message from server");
account.setLastReceiveTimestamp(System.currentTimeMillis());
try { try {
var result = signalWebSocket.readOrEmpty(unit.toMillis(timeout), envelope1 -> { var result = signalWebSocket.readOrEmpty(unit.toMillis(timeout), envelope1 -> {
final var recipientId = envelope1.hasSourceUuid() final var recipientId = envelope1.hasSourceUuid()
@ -955,7 +959,7 @@ public class ManagerImpl implements Manager {
} else { } else {
throw e; throw e;
} }
} catch (WebSocketUnavailableException e) { } catch (IOException e) {
logger.debug("Pipe unexpectedly unavailable, connecting"); logger.debug("Pipe unexpectedly unavailable, connecting");
signalWebSocket.connect(); signalWebSocket.connect();
continue; continue;

View file

@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.KeyHelper;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceAccountManager.NewDeviceRegistrationReturn;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@ -90,7 +91,13 @@ public class ProvisioningManager {
} }
public Manager finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists { public Manager finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
var ret = accountManager.getNewDeviceRegistration(tempIdentityKey); NewDeviceRegistrationReturn ret;
logger.info("Waiting for addDevice request...");
try {
ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
} catch (IOException | TimeoutException e) {
throw new TimeoutException(e.getMessage());
}
var number = ret.getNumber(); var number = ret.getNumber();
logger.info("Received link information from {}, linking in progress ...", number); logger.info("Received link information from {}, linking in progress ...", number);

View file

@ -70,7 +70,7 @@ Exceptions: None
listen(number<s>) -> <>:: listen(number<s>) -> <>::
* number : Phone number * number : Phone number
Starting checking the Signal servers on behalf of this number, and export a DBus object path for it. Start checking the Signal servers on behalf of this number, and export a DBus object path for it.
Fails if user is not already registered. Fails if user is not already registered.
Exceptions: Failure Exceptions: Failure
@ -90,6 +90,23 @@ Captcha strings may be obtained from `https://signalcaptchas.org/registration/ge
Exceptions: Failure, InvalidNumber, RequiresCaptcha Exceptions: Failure, InvalidNumber, RequiresCaptcha
unlisten(number<s>) -> <>::
* number : Phone number
* keepData : true or omitted = keep files in data directory; false = delete files
Stops the current device from listening to DBus. In single-user mode, kills the daemon.
Exception: Failure
unregister(number<s>) -> <>::
unregister(number<s>, keepData<b>) -> <>::
* number : Phone number
* keepData : true or omitted = keep files in data directory; false = delete files
Unregisters the current device. In single-user mode, kills the daemon.
Exception: Failure
verify(number<s>, verificationCode<s>) -> <>:: verify(number<s>, verificationCode<s>) -> <>::
* number : Phone number * number : Phone number
* verificationCode : Code received from Signal after successful registration request * verificationCode : Code received from Signal after successful registration request
@ -173,7 +190,7 @@ isMember(groupId<ay>) -> active<b>::
Note that this method does not raise an Exception for a non-existing/unknown group but will simply return 0 (false) Note that this method does not raise an Exception for a non-existing/unknown group but will simply return 0 (false)
sendEndSessionMessage(recipients<as>) -> <>:: sendEndSessionMessage(recipients<as>) -> <>::
* recipients : Array of phone numbers * recipients : Array of phone numbers
Exceptions: Failure, InvalidNumber, UntrustedIdentity Exceptions: Failure, InvalidNumber, UntrustedIdentity
@ -209,7 +226,7 @@ sendMessage(message<s>, attachments<as>, recipients<as>) -> timestamp<x>::
* message : Text to send (can be UTF8) * message : Text to send (can be UTF8)
* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under) * attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
* recipient : Phone number of a single recipient * recipient : Phone number of a single recipient
* recipients : Array of phone numbers * recipients : Array of phone numbers
* timestamp : Can be used to identify the corresponding signal reply * timestamp : Can be used to identify the corresponding signal reply
Depending on the type of the recipient field this sends a message to one or multiple recipients. Depending on the type of the recipient field this sends a message to one or multiple recipients.
@ -285,7 +302,7 @@ groupList : Array of Byte arrays representing the internal group identifiers
All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked() All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked()
getGroupName(groupId<ay>) -> groupName<s>:: getGroupName(groupId<ay>) -> groupName<s>::
groupName : The display name of the group groupName : The display name of the group
groupId : Byte array representing the internal group identifier groupId : Byte array representing the internal group identifier
Exceptions: None, if the group name is not found an empty string is returned Exceptions: None, if the group name is not found an empty string is returned
@ -361,7 +378,7 @@ removeDevice(deviceId<i>) -> <>::
Exception: Failure Exception: Failure
updateDeviceName(deviceName<s>) -> <>:: updateDeviceName(deviceName<s>) -> <>::
* deviceName : New name * deviceName : New name
Set a new name for this device (main or linked). Set a new name for this device (main or linked).

View file

@ -8,6 +8,7 @@ import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.interfaces.Properties; import org.freedesktop.dbus.interfaces.Properties;
import org.freedesktop.dbus.messages.DBusSignal; import org.freedesktop.dbus.messages.DBusSignal;
import java.io.IOException;
import java.util.List; import java.util.List;
/** /**
@ -16,7 +17,7 @@ import java.util.List;
*/ */
public interface Signal extends DBusInterface { public interface Signal extends DBusInterface {
String getSelfNumber(); String getSelfNumber();
long sendMessage( long sendMessage(
String message, List<String> attachments, String recipient String message, List<String> attachments, String recipient

View file

@ -1,5 +1,6 @@
package org.asamk; package org.asamk;
import org.asamk.Signal.Error;
import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.freedesktop.dbus.interfaces.DBusInterface; import org.freedesktop.dbus.interfaces.DBusInterface;
@ -24,12 +25,18 @@ public interface SignalControl extends DBusInterface {
void verifyWithPin(String number, String verificationCode, String pin) throws Error.Failure, Error.InvalidNumber; void verifyWithPin(String number, String verificationCode, String pin) throws Error.Failure, Error.InvalidNumber;
void listen(String number) throws Error.Failure;
void unlisten(String number) throws Error.Failure;
void unregister(String number) throws Error.Failure;
void unregister(String number, boolean keepData) throws Error.Failure;
String link(String newDeviceName) throws Error.Failure; String link(String newDeviceName) throws Error.Failure;
public String version(); public String version();
void listen(String number) throws Error.Failure;
List<DBusPath> listAccounts(); List<DBusPath> listAccounts();
interface Error { interface Error {

View file

@ -216,7 +216,10 @@ public class DaemonCommand implements MultiLocalCommand {
initThread.join(); initThread.join();
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} }
signal.close(); try {
signal.close();
} catch (IOException ignored) {
}
}); });
thread.start(); thread.start();

View file

@ -10,7 +10,7 @@ import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import java.util.List; import java.util.List;
public interface MultiLocalCommand extends LocalCommand { public interface MultiLocalCommand extends LocalCommand {
void handleCommand(Namespace ns, List<Manager> m, SignalCreator c, OutputWriter outputWriter, TrustNewIdentity trustNewIdentity) throws CommandException; void handleCommand(Namespace ns, List<Manager> managers, 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, Manager m, SignalCreator c, OutputWriter outputWriter, TrustNewIdentity trustNewIdentity) throws CommandException;
} }

View file

@ -13,11 +13,15 @@ import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.DaemonCommand; import org.asamk.signal.commands.DaemonCommand;
import org.asamk.signal.commands.SignalCreator; import org.asamk.signal.commands.SignalCreator;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.PathConfig;
import org.asamk.signal.manager.ProvisioningManager; import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.UserAlreadyExists; import org.asamk.signal.manager.UserAlreadyExists;
import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.DBusPath;
@ -38,7 +42,11 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URI; import java.net.URI;
import java.nio.channels.OverlappingFileLockException; import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -46,6 +54,7 @@ import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DbusSignalControlImpl implements org.asamk.SignalControl { public class DbusSignalControlImpl implements org.asamk.SignalControl {
@ -163,6 +172,53 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
} }
} }
@Override
public void unregister(String number) {
unregister(number, true);
}
@Override
public void unregister(String number, boolean keepData) {
try {
List<Manager> managers = new ArrayList<>();
Manager manager = null;
synchronized (receiveThreads) {
managers = receiveThreads.stream()
.map(Pair::first)
.collect(Collectors.toList());
}
if (managers.size() == 0) {
throw new Error.Failure("Unregister error: no manager found.");
}
for (Manager m : managers) {
try {
m.getSelfNumber();
} catch (NullPointerException ignore) {
continue;
}
if (m.getSelfNumber().equals(number)) {
manager = m;
break;
}
}
if (manager == null) {
throw new Error.Failure("Unregister error, " + number + " is not listening.");
}
manager.unregister();
DBusConnection.DBusBusType busType = DaemonCommand.dBusType;
if (!keepData) {
removeUserData(number);
}
unlisten(number);
} catch (Exception e) {
throw new Error.Failure(e.getClass().getSimpleName() + " Unregister error: " + e.getMessage());
}
}
@Override @Override
public String link(final String newDeviceName) throws Error.Failure { public String link(final String newDeviceName) throws Error.Failure {
try { try {
@ -242,6 +298,59 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
} }
} }
@Override
public void unlisten(String number) {
try {
List<Manager> managers = new ArrayList<>();
Manager manager = null;
synchronized (receiveThreads) {
managers = receiveThreads.stream()
.map(Pair::first)
.collect(Collectors.toList());
}
if (managers.size() == 0) {
throw new Error.Failure("Unlisten error: no manager found.");
}
for (Manager m : managers) {
try {
m.getSelfNumber();
} catch (NullPointerException ignore) {
continue;
}
if (m.getSelfNumber().equals(number)) {
manager = m;
break;
}
}
if (manager == null) {
throw new Error.Failure("Unlisten error, " + number + " is already not listening.");
}
String objectPath = DbusConfig.getObjectPath(number);
DBusConnection.DBusBusType busType = DaemonCommand.dBusType;
var conn = DBusConnection.getConnection(busType);
//if single-user mode, just close the manager because we're exiting anyway
//else unexport the object
try {
//this will generate an error if we are in anonymous mode
conn.exportObject(new DbusSignalImpl(manager, conn, objectPath));
//no error, hence single-user mode
manager.close();
logger.info("unExported dbus object: " + DbusConfig.getObjectPath());
} catch (DBusException ignore) {
//anonymous mode
conn.unExportObject(objectPath);
manager.close();
logger.info("unExported dbus object: " + objectPath);
}
} catch (IOException | DBusException e) {
throw new Error.Failure(e.getClass().getSimpleName() + " Unlisten error: " + e.getMessage());
}
}
@Override @Override
public List<DBusPath> listAccounts() { public List<DBusPath> listAccounts() {
synchronized (receiveThreads) { synchronized (receiveThreads) {
@ -252,4 +361,25 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }
private void removeUserData(String number) {
File dataPath = PathConfig.createDefault(c.getSettingsPath()).getDataPath();
number.replaceFirst("_", "+");
String eraseFileName = dataPath.getAbsolutePath() + File.separator + number;
File eraseFile = new File(eraseFileName);
if (eraseFile.delete()) {
logger.info("erased " + eraseFileName);
} else {
logger.error("erase failed for " + eraseFileName);
}
String erasePath = dataPath.getAbsolutePath() + File.separator + number + ".d/";
Path rootPath = Paths.get(erasePath);
try (Stream<Path> walk = Files.walk(rootPath)) {
walk.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
} catch (IOException e) {
throw new Error.Failure(e.getClass().getSimpleName() + " RemoveUserData failed. " + e.getMessage());
}
}
} }

View file

@ -1,7 +1,10 @@
package org.asamk.signal.dbus; package org.asamk.signal.dbus;
import org.asamk.Signal; import org.asamk.Signal;
import org.asamk.Signal.Error;
import org.asamk.signal.BaseConfig; import org.asamk.signal.BaseConfig;
import org.asamk.signal.DbusConfig;
import org.asamk.signal.commands.DaemonCommand;
import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException; import org.asamk.signal.manager.NotMasterDeviceException;
@ -20,10 +23,13 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.util.ErrorUtils; import org.asamk.signal.util.ErrorUtils;
import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -35,16 +41,21 @@ import org.whispersystems.signalservice.internal.contacts.crypto.Unauthenticated
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -53,6 +64,7 @@ public class DbusSignalImpl implements Signal {
private final Manager m; private final Manager m;
private final DBusConnection connection; private final DBusConnection connection;
private final String objectPath; private final String objectPath;
private final static Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class);
private DBusPath thisDevice; private DBusPath thisDevice;
private final List<DBusPath> devices = new ArrayList<>(); private final List<DBusPath> devices = new ArrayList<>();
@ -67,8 +79,9 @@ public class DbusSignalImpl implements Signal {
updateDevices(); updateDevices();
} }
public void close() { public void close() throws IOException {
unExportDevices(); unExportDevices();
m.close();
} }
@Override @Override