# signal-cli
-signal-cli is a commandline interface for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering, verifying, sending and receiving messages.
-To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21).
-For registering you need a phone number where you can receive SMS or incoming calls.
-signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to send messages from any programming language that has dbus bindings.
-It also has a JSON-RPC based interface, see the [documentation](https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service) for more information.
+signal-cli is a commandline interface
+for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering,
+verifying, sending and receiving messages. To be able to link to an existing Signal-Android/signal-cli instance,
+signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because
+libsignal-service-java does not yet
+support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). For
+registering you need a phone number where you can receive SMS or incoming calls. signal-cli is primarily intended to be
+used on servers to notify admins of important events. For this use-case, it has a dbus
+interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to
+send messages from any programming language that has dbus bindings. It also has a JSON-RPC based interface, see
+the [documentation](https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service) for more information.
## Installation
-You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well.
+You can [build signal-cli](#building) yourself, or use
+the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and
+Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is
+a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well.
System requirements:
+
- at least Java Runtime Environment (JRE) 17
- native libraries: libzkgroup, libsignal-client
- Those are bundled for x86_64 Linux (with recent enough glibc, see #643), for other systems/architectures see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
+ Those are bundled for x86_64 Linux (with recent enough glibc, see #643), for other systems/architectures
+ see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
### Install system-wide on Linux
+
See [latest version](https://github.com/AsamK/signal-cli/releases).
+
```sh
export VERSION=<latest version, format "x.y.z">
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
```
+
You can find further instructions on the Wiki:
+
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service)
## Usage
-For a complete usage overview please read the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and the [wiki](https://github.com/AsamK/signal-cli/wiki).
+For a complete usage overview please read
+the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and
+the [wiki](https://github.com/AsamK/signal-cli/wiki).
-Important: The USERNAME is your phone number in international format and must include the country calling code. Hence it should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list of all country codes.)
+Important: The ACCOUNT is your phone number in international format and must include the country calling code. Hence it
+should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list
+of all country codes.)
* Register a number (with SMS verification)
- signal-cli -u USERNAME register
+ signal-cli -a ACCOUNT register
- You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly to the voice call verification by adding the `--voice` switch at the end of above register command.
+ You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly
+ to the voice call verification by adding the `--voice` switch at the end of above register command.
- Registering may require solving a CAPTCHA challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
+ Registering may require solving a CAPTCHA
+ challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
-* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code to your account
+* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code
+ to your account
- signal-cli -u USERNAME verify CODE
+ signal-cli -a ACCOUNT verify CODE
* Send a message
- signal-cli -u USERNAME send -m "This is a message" RECIPIENT
+ signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT
* Pipe the message content from another process.
- uname -a | signal-cli -u USERNAME send RECIPIENT
+ uname -a | signal-cli -a ACCOUNT send RECIPIENT
* Receive messages
- signal-cli -u USERNAME receive
+ signal-cli -a ACCOUNT receive
-**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive` command).
-This is required for the encryption to work efficiently and for getting updates to groups, expiration timer and other features.
+**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive`
+command). This is required for the encryption to work efficiently and for getting updates to groups, expiration timer
+and other features.
## Storage
## Building
-This project uses [Gradle](http://gradle.org) for building and maintaining
-dependencies. If you have a recent gradle version installed, you can replace `./gradlew` with `gradle` in the following steps.
+This project uses [Gradle](http://gradle.org) for building and maintaining dependencies. If you have a recent gradle
+version installed, you can replace `./gradlew` with `gradle` in the following steps.
1. Checkout the source somewhere on your filesystem with
./gradlew build
- 2a. Create shell wrapper in *build/install/signal-cli/bin*:
+ 2a. Create shell wrapper in *build/install/signal-cli/bin*:
./gradlew installDist
- 2b. Create tar file in *build/distributions*:
+ 2b. Create tar file in *build/distributions*:
./gradlew distTar
- 2c. Create a fat tar file in *build/libs/signal-cli-fat*:
+ 2c. Create a fat tar file in *build/libs/signal-cli-fat*:
./gradlew fatJar
- 2d. Compile and run signal-cli:
+ 2d. Compile and run signal-cli:
./gradlew run --args="--help"
### Building a native binary with GraalVM (EXPERIMENTAL)
-It is possible to build a native binary with [GraalVM](https://www.graalvm.org).
-This is still experimental and will not work in all situations.
+It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not
+work in all situations.
1. [Install GraalVM and setup the enviroment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
2. [Install prerequisites](https://www.graalvm.org/reference-manual/native-image/#prerequisites)
The binary is available at *build/native/nativeCompile/signal-cli*
## FAQ and Troubleshooting
+
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ)
## License
[Service]
Type=dbus
Environment="SIGNAL_CLI_OPTS=-Xms2m"
-ExecStart=%dir%/bin/signal-cli -u %I --config /var/lib/signal-cli daemon --system
+ExecStart=%dir%/bin/signal-cli -a %I --config /var/lib/signal-cli daemon --system
User=signal-cli
BusName=org.asamk.Signal
# JVM always exits with 143 in reaction to SIGTERM signal
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT\\E"
},
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MO\\E"
+ },
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PA\\E"
},
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(),
- account.getUsername(),
+ account.getAccount(),
account.getPassword(),
account.getDeviceId());
final var sessionLock = new SignalSessionLock() {
@Override
public String getSelfNumber() {
- return account.getUsername();
+ return account.getAccount();
}
@Override
public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
try {
- return PhoneNumberFormatter.formatNumber(n, account.getUsername());
+ return PhoneNumberFormatter.formatNumber(n, account.getAccount());
} catch (InvalidNumberException e) {
return "";
}
/**
* Trust this the identity with this fingerprint
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
* @param fingerprint Fingerprint
*/
@Override
/**
* Trust this the identity with this safety number
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
* @param safetyNumber Safety number
*/
@Override
/**
* Trust this the identity with this scannable safety number
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
* @param safetyNumber Scannable safety number
*/
@Override
/**
* Trust all keys of this identity without verification
*
- * @param recipient username of the identity
+ * @param recipient account of the identity
*/
@Override
public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) {
ProvisioningManager getNewProvisioningManager();
- RegistrationManager getNewRegistrationManager(String username) throws IOException;
+ RegistrationManager getNewRegistrationManager(String account) throws IOException;
@Override
void close();
this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
new DynamicCredentialsProvider(
// Using empty UUID, because registering doesn't work otherwise
- null, account.getUsername(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
+ null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
userAgent,
groupsV2Operations,
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
try {
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
new DynamicCredentialsProvider(account.getAci(),
- account.getUsername(),
+ account.getAccount(),
account.getPassword(),
account.getDeviceId()),
userAgent,
return;
}
- if (!accountRecord.getE164().equals(account.getUsername())) {
+ if (!accountRecord.getE164().equals(account.getAccount())) {
// TODO implement changed number handling
}
private final FileChannel fileChannel;
private final FileLock lock;
- private String username;
+ private String account;
private ACI aci;
private String encryptedDeviceName;
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
}
public static SignalAccount load(
- File dataPath, String username, boolean waitForLock, final TrustNewIdentity trustNewIdentity
+ File dataPath, String account, boolean waitForLock, final TrustNewIdentity trustNewIdentity
) throws IOException {
- final var fileName = getFileName(dataPath, username);
+ final var fileName = getFileName(dataPath, account);
final var pair = openFileChannel(fileName, waitForLock);
try {
- var account = new SignalAccount(pair.first(), pair.second());
- account.load(dataPath, trustNewIdentity);
- account.migrateLegacyConfigs();
+ var signalAccount = new SignalAccount(pair.first(), pair.second());
+ signalAccount.load(dataPath, trustNewIdentity);
+ signalAccount.migrateLegacyConfigs();
- if (!username.equals(account.getUsername())) {
- throw new IOException("Username in account file doesn't match expected number: "
- + account.getUsername());
+ if (!account.equals(signalAccount.getAccount())) {
+ throw new IOException("Number in account file doesn't match expected number: "
+ + signalAccount.getAccount());
}
- return account;
+ return signalAccount;
} catch (Throwable e) {
pair.second().close();
pair.first().close();
public static SignalAccount create(
File dataPath,
- String username,
+ String account,
IdentityKeyPair identityKey,
int registrationId,
ProfileKey profileKey,
final TrustNewIdentity trustNewIdentity
) throws IOException {
IOUtils.createPrivateDirectories(dataPath);
- var fileName = getFileName(dataPath, username);
+ var fileName = getFileName(dataPath, account);
if (!fileName.exists()) {
IOUtils.createPrivateFile(fileName);
}
final var pair = openFileChannel(fileName, true);
- var account = new SignalAccount(pair.first(), pair.second());
+ var signalAccount = new SignalAccount(pair.first(), pair.second());
- account.username = username;
- account.profileKey = profileKey;
+ signalAccount.account = account;
+ signalAccount.profileKey = profileKey;
- account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
- account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
- account.recipientStore,
- account::saveGroupStore);
- account.stickerStore = new StickerStore(account::saveStickerStore);
- account.configurationStore = new ConfigurationStore(account::saveConfigurationStore);
+ signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
+ signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
+ signalAccount.recipientStore,
+ signalAccount::saveGroupStore);
+ signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
+ signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
- account.registered = false;
+ signalAccount.registered = false;
- account.migrateLegacyConfigs();
- account.save();
+ signalAccount.migrateLegacyConfigs();
+ signalAccount.save();
- return account;
+ return signalAccount;
}
private void initStores(
final int registrationId,
final TrustNewIdentity trustNewIdentity
) throws IOException {
- recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
+ recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account), this::mergeRecipients);
- preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
- signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
- sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore);
- identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
+ preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account));
+ signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account));
+ sessionStore = new SessionStore(getSessionsPath(dataPath, account), recipientStore);
+ identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account),
recipientStore,
identityKey,
registrationId,
trustNewIdentity);
- senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, username),
- getSenderKeysPath(dataPath, username),
+ senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account),
+ getSenderKeysPath(dataPath, account),
recipientStore::resolveRecipientAddress,
recipientStore);
signalProtocolStore = new SignalProtocolStore(preKeyStore,
senderKeyStore,
this::isMultiDevice);
- messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+ messageCache = new MessageCache(getMessageCachePath(dataPath, account));
}
public static SignalAccount createOrUpdateLinkedAccount(
File dataPath,
- String username,
+ String account,
ACI aci,
String password,
String encryptedDeviceName,
final TrustNewIdentity trustNewIdentity
) throws IOException {
IOUtils.createPrivateDirectories(dataPath);
- var fileName = getFileName(dataPath, username);
+ var fileName = getFileName(dataPath, account);
if (!fileName.exists()) {
return createLinkedAccount(dataPath,
- username,
+ account,
aci,
password,
encryptedDeviceName,
trustNewIdentity);
}
- final var account = load(dataPath, username, true, trustNewIdentity);
- account.setProvisioningData(username, aci, password, encryptedDeviceName, deviceId, profileKey);
- account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
- account.sessionStore.archiveAllSessions();
- account.senderKeyStore.deleteAll();
- account.clearAllPreKeys();
- return account;
+ final var signalAccount = load(dataPath, account, true, trustNewIdentity);
+ signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
+ signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress());
+ signalAccount.sessionStore.archiveAllSessions();
+ signalAccount.senderKeyStore.deleteAll();
+ signalAccount.clearAllPreKeys();
+ return signalAccount;
}
private void clearAllPreKeys() {
private static SignalAccount createLinkedAccount(
File dataPath,
- String username,
+ String account,
ACI aci,
String password,
String encryptedDeviceName,
ProfileKey profileKey,
final TrustNewIdentity trustNewIdentity
) throws IOException {
- var fileName = getFileName(dataPath, username);
+ var fileName = getFileName(dataPath, account);
IOUtils.createPrivateFile(fileName);
final var pair = openFileChannel(fileName, true);
- var account = new SignalAccount(pair.first(), pair.second());
+ var signalAccount = new SignalAccount(pair.first(), pair.second());
- account.setProvisioningData(username, aci, password, encryptedDeviceName, deviceId, profileKey);
+ signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
- account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
- account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
- account.recipientStore,
- account::saveGroupStore);
- account.stickerStore = new StickerStore(account::saveStickerStore);
- account.configurationStore = new ConfigurationStore(account::saveConfigurationStore);
+ signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
+ signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
+ signalAccount.recipientStore,
+ signalAccount::saveGroupStore);
+ signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
+ signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
- account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
- account.migrateLegacyConfigs();
- account.save();
+ signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress());
+ signalAccount.migrateLegacyConfigs();
+ signalAccount.save();
- return account;
+ return signalAccount;
}
private void setProvisioningData(
- final String username,
+ final String account,
final ACI aci,
final String password,
final String encryptedDeviceName,
final int deviceId,
final ProfileKey profileKey
) {
- this.username = username;
+ this.account = account;
this.aci = aci;
this.password = password;
this.profileKey = profileKey;
senderKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
}
- public static File getFileName(File dataPath, String username) {
- return new File(dataPath, username);
+ public static File getFileName(File dataPath, String account) {
+ return new File(dataPath, account);
}
- private static File getUserPath(final File dataPath, final String username) {
- final var path = new File(dataPath, username + ".d");
+ private static File getUserPath(final File dataPath, final String account) {
+ final var path = new File(dataPath, account + ".d");
try {
IOUtils.createPrivateDirectories(path);
} catch (IOException e) {
return path;
}
- private static File getMessageCachePath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "msg-cache");
+ private static File getMessageCachePath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "msg-cache");
}
- private static File getGroupCachePath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "group-cache");
+ private static File getGroupCachePath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "group-cache");
}
- private static File getPreKeysPath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "pre-keys");
+ private static File getPreKeysPath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "pre-keys");
}
- private static File getSignedPreKeysPath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "signed-pre-keys");
+ private static File getSignedPreKeysPath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "signed-pre-keys");
}
- private static File getIdentitiesPath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "identities");
+ private static File getIdentitiesPath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "identities");
}
- private static File getSessionsPath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "sessions");
+ private static File getSessionsPath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "sessions");
}
- private static File getSenderKeysPath(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "sender-keys");
+ private static File getSenderKeysPath(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "sender-keys");
}
- private static File getSharedSenderKeysFile(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "shared-sender-keys-store");
+ private static File getSharedSenderKeysFile(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
}
- private static File getRecipientsStoreFile(File dataPath, String username) {
- return new File(getUserPath(dataPath, username), "recipients-store");
+ private static File getRecipientsStoreFile(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "recipients-store");
}
- public static boolean userExists(File dataPath, String username) {
- if (username == null) {
+ public static boolean userExists(File dataPath, String account) {
+ if (account == null) {
return false;
}
- var f = getFileName(dataPath, username);
+ var f = getFileName(dataPath, account);
return !(!f.exists() || f.isDirectory());
}
}
}
- username = Utils.getNotNullNode(rootNode, "username").asText();
+ account = Utils.getNotNullNode(rootNode, "username").asText();
password = Utils.getNotNullNode(rootNode, "password").asText();
registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
if (rootNode.hasNonNull("uuid")) {
if (rootNode.hasNonNull("groupStore")) {
groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
groupStore = GroupStore.fromStorage(groupStoreStorage,
- getGroupCachePath(dataPath, username),
+ getGroupCachePath(dataPath, account),
recipientStore,
this::saveGroupStore);
} else {
- groupStore = new GroupStore(getGroupCachePath(dataPath, username), recipientStore, this::saveGroupStore);
+ groupStore = new GroupStore(getGroupCachePath(dataPath, account), recipientStore, this::saveGroupStore);
}
if (rootNode.hasNonNull("stickerStore")) {
synchronized (fileChannel) {
var rootNode = jsonProcessor.createObjectNode();
rootNode.put("version", CURRENT_STORAGE_VERSION)
- .put("username", username)
+ .put("username", account)
.put("uuid", aci == null ? null : aci.toString())
.put("deviceName", encryptedDeviceName)
.put("deviceId", deviceId)
return messageCache;
}
- public String getUsername() {
- return username;
+ public String getAccount() {
+ return account;
}
public ACI getAci() {
}
public SignalServiceAddress getSelfAddress() {
- return new SignalServiceAddress(aci, username);
+ return new SignalServiceAddress(aci, account);
}
public RecipientId getSelfRecipientId() {
- return recipientStore.resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), username));
+ return recipientStore.resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), account));
}
public String getEncryptedDeviceName() {
== Synopsis
-*signal-cli* [--verbose] [--config CONFIG] [-u USERNAME] [-o {plain-text,json}] daemon [--system]
+*signal-cli* [--verbose] [--config CONFIG] [-a ACCOUNT] [-o {plain-text,json}] daemon [--system]
*dbus-send* [--system | --session] [--print-reply] --type=method_call --dest="org.asamk.Signal" /org/asamk/Signal[/_<phonenum>] org.asamk.Signal.<method> [string:<string argument>] [array:<type>:<array argument>]
-Note: when daemon was started without explicit `-u USERNAME`, the `dbus-send` command requires adding the phone number in `/org/asamk/Signal/_<phonenum>`.
+Note: when daemon was started without explicit `-a ACCOUNT`, the `dbus-send` command requires adding the phone number in `/org/asamk/Signal/_<phonenum>`.
== Description
== Methods
=== Control methods
-These methods are available if the daemon is started anonymously (without an explicit `-u USERNAME`).
+These methods are available if the daemon is started anonymously (without an explicit `-a ACCOUNT`).
Requests are sent to `/org/asamk/Signal`; requests related to individual accounts are sent to
`/org/asamk/Signal/_441234567890` where the + dialing code is replaced by an underscore (_).
Only `version()` is activated in single-account mode; the rest are disabled.
Send a group message::
dbus-send --session --print-reply --type=method_call --dest=org.asamk.Signal /org/asamk/Signal org.asamk.Signal.sendGroupMessage string:'The message goes here' array:string:'/path/to/attachmnt1','/path/to/attachmnt2' array:byte:139,22,72,247,116,32,170,104,205,164,207,21,248,77,185
-Print the group name corresponding to a groupId; the daemon runs on system bus, and was started without an explicit `-u USERNAME`::
+Print the group name corresponding to a groupId; the daemon runs on system bus, and was started without an explicit `-a ACCOUNT`::
dbus-send --system --print-reply --type=method_call --dest='org.asamk.Signal' /org/asamk/Signal/_1234567890 org.asamk.Signal.getGroupName array:byte:139,22,72,247,116,32,170,104,205,164,207,21,248,77,185
== Authors
== Synopsis
-*signal-cli* [--config CONFIG] [-h | -v | -u USERNAME | --dbus | --dbus-system] command [command-options]
+*signal-cli* [--config CONFIG] [-h | -v | -a ACCOUNT | --dbus | --dbus-system] command [command-options]
== Description
Make sure you have full read/write access to the given directory.
(Default: `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`))
-*-u* USERNAME, *--username* USERNAME::
+*-a* ACCOUNT, *--account* ACCOUNT::
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.
=== 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
+If no `-a` account is given, all local accounts will be exported as separate dbus
objects under the same bus name.
*--system*::
== Examples
Register a number (with SMS verification)::
-signal-cli -u USERNAME register
+signal-cli -a ACCOUNT register
Verify the number using the code received via SMS or voice::
-signal-cli -u USERNAME verify CODE
+signal-cli -a ACCOUNT verify CODE
Send a message to one or more recipients::
-signal-cli -u USERNAME send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]]
+signal-cli -a ACCOUNT send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]]
Pipe the message content from another process::
-uname -a | signal-cli -u USERNAME send [RECIPIENT [RECIPIENT ...]]
+uname -a | signal-cli -a ACCOUNT send [RECIPIENT [RECIPIENT ...]]
Create a group::
-signal-cli -u USERNAME updateGroup -n "Group name" -m [MEMBER [MEMBER ...]]
+signal-cli -a ACCOUNT updateGroup -n "Group name" -m [MEMBER [MEMBER ...]]
Add member to a group::
-signal-cli -u USERNAME updateGroup -g GROUP_ID -m "NEW_MEMBER"
+signal-cli -a ACCOUNT updateGroup -g GROUP_ID -m "NEW_MEMBER"
Accept a group invitation::
-signal-cli -u USERNAME updateGroup -g GROUP_ID
+signal-cli -a ACCOUNT updateGroup -g GROUP_ID
Leave a group::
-signal-cli -u USERNAME quitGroup -g GROUP_ID
+signal-cli -a ACCOUNT quitGroup -g GROUP_ID
Send a message to a group::
-signal-cli -u USERNAME send -m "This is a message" -g GROUP_ID
+signal-cli -a ACCOUNT send -m "This is a message" -g GROUP_ID
Trust new key, after having verified it::
-signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER
+signal-cli -a ACCOUNT 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
+signal-cli -a ACCOUNT trust -a NUMBER
== Exit codes
* *1*: Error is probably caused and fixable by the user
PIN=$2
echo -n "Enter a captcha token (https://signalcaptchas.org/staging/challenge/generate.html): "
read CAPTCHA
- run_main -u "$NUMBER" register --captcha "$CAPTCHA"
+ run_main -a "$NUMBER" register --captcha "$CAPTCHA"
echo -n "Enter validation code for ${NUMBER}: "
read CODE
if [ -z "$PIN" ]; then
- run_main -u "$NUMBER" verify "$CODE"
+ run_main -a "$NUMBER" verify "$CODE"
else
- run_main -u "$NUMBER" verify "$CODE" --pin "$PIN"
+ run_main -a "$NUMBER" verify "$CODE" --pin "$PIN"
fi
}
mkfifo "$LINK_CODE_FILE"
run_linked link -n "test-device" >"$LINK_CODE_FILE" &
read LINK_CODE <"$LINK_CODE_FILE"
- run_main -u "$NUMBER" addDevice --uri "$LINK_CODE"
+ run_main -a "$NUMBER" addDevice --uri "$LINK_CODE"
wait
- run_linked -u "$NUMBER" send --note-to-self -m hi
- run_main -u "$NUMBER" receive
- run_linked -u "$NUMBER" receive
- run_main -u "$NUMBER" receive
+ run_linked -a "$NUMBER" send --note-to-self -m hi
+ run_main -a "$NUMBER" receive
+ run_linked -a "$NUMBER" receive
+ run_main -a "$NUMBER" receive
}
run_main --version
## DBus
-#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m daemon_not_running || true
+#run_main -a "$NUMBER_1" --dbus send "$NUMBER_2" -m daemon_not_running || true
#run_main daemon &
#DAEMON_PID=$!
#sleep 10
-#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m hii
-#run_main -u "$NUMBER_2" --dbus receive
+#run_main -a "$NUMBER_1" --dbus send "$NUMBER_2" -m hii
+#run_main -a "$NUMBER_2" --dbus receive
#kill "$DAEMON_PID"
rm -f "$FIFO_FILE"
mkfifo "$FIFO_FILE"
-run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
-run_main -u "$NUMBER_2" jsonRpc < "$FIFO_FILE" &
+run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
+run_main -a "$NUMBER_2" jsonRpc < "$FIFO_FILE" &
exec 3<> "$FIFO_FILE"
echo '{"jsonrpc":"2.0","id":"id","method":"updateContact","params":{"recipient":"'"$NUMBER_1"'","name":"NUMBER_1","expiration":10}}' >&3
wait
-run_main -u "$NUMBER_1" setPin "$TEST_PIN_1"
-run_main -u "$NUMBER_2" removePin
+run_main -a "$NUMBER_1" setPin "$TEST_PIN_1"
+run_main -a "$NUMBER_2" removePin
## Contacts
-run_main -u "$NUMBER_2" updateContact "$NUMBER_1" -n NUMBER_1 -e 10
-run_main -u "$NUMBER_2" block "$NUMBER_1"
-run_main -u "$NUMBER_2" unblock "$NUMBER_1"
-run_main -u "$NUMBER_2" listContacts
-
-run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
-run_main -u "$NUMBER_2" receive
-run_main -u "$NUMBER_2" send "$NUMBER_1" -m hi
-run_main -u "$NUMBER_1" receive
-run_main -u "$NUMBER_2" receive
+run_main -a "$NUMBER_2" updateContact "$NUMBER_1" -n NUMBER_1 -e 10
+run_main -a "$NUMBER_2" block "$NUMBER_1"
+run_main -a "$NUMBER_2" unblock "$NUMBER_1"
+run_main -a "$NUMBER_2" listContacts
+
+run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
+run_main -a "$NUMBER_2" receive
+run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi
+run_main -a "$NUMBER_1" receive
+run_main -a "$NUMBER_2" receive
## Groups
-GROUP_ID=$(run_main -u "$NUMBER_1" updateGroup -n GRUPPE -a LICENSE -m "$NUMBER_1" | grep -oP '(?<=").+(?=")')
-run_main -u "$NUMBER_1" send "$NUMBER_2" -m first
-run_main -u "$NUMBER_1" updateGroup -g "$GROUP_ID" -n GRUPPE_UMB -m "$NUMBER_2" --admin "$NUMBER_2" --remove-admin "$NUMBER_2" --description DESCRIPTION --link=enabled-with-approval --set-permission-add-member=only-admins --set-permission-edit-details=only-admins -e 42
-run_main -u "$NUMBER_1" listGroups -d
-run_main -u "$NUMBER_1" --output=json listGroups -d
-run_main -u "$NUMBER_2" --verbose receive
-run_main -u "$NUMBER_2" quitGroup -g "$GROUP_ID"
-run_main -u "$NUMBER_2" listGroups -d
-run_main -u "$NUMBER_2" --output=json listGroups -d
-run_main -u "$NUMBER_1" receive
-run_main -u "$NUMBER_1" updateGroup -g "$GROUP_ID" -m "$NUMBER_2"
-run_main -u "$NUMBER_1" --verbose block -g "$GROUP_ID"
-run_main -u "$NUMBER_1" --verbose unblock -g "$GROUP_ID"
+GROUP_ID=$(run_main -a "$NUMBER_1" updateGroup -n GRUPPE -a LICENSE -m "$NUMBER_1" | grep -oP '(?<=").+(?=")')
+run_main -a "$NUMBER_1" send "$NUMBER_2" -m first
+run_main -a "$NUMBER_1" updateGroup -g "$GROUP_ID" -n GRUPPE_UMB -m "$NUMBER_2" --admin "$NUMBER_2" --remove-admin "$NUMBER_2" --description DESCRIPTION --link=enabled-with-approval --set-permission-add-member=only-admins --set-permission-edit-details=only-admins -e 42
+run_main -a "$NUMBER_1" listGroups -d
+run_main -a "$NUMBER_1" --output=json listGroups -d
+run_main -a "$NUMBER_2" --verbose receive
+run_main -a "$NUMBER_2" quitGroup -g "$GROUP_ID"
+run_main -a "$NUMBER_2" listGroups -d
+run_main -a "$NUMBER_2" --output=json listGroups -d
+run_main -a "$NUMBER_1" receive
+run_main -a "$NUMBER_1" updateGroup -g "$GROUP_ID" -m "$NUMBER_2"
+run_main -a "$NUMBER_1" --verbose block -g "$GROUP_ID"
+run_main -a "$NUMBER_1" --verbose unblock -g "$GROUP_ID"
## Identities
-run_main -u "$NUMBER_1" listIdentities
-run_main -u "$NUMBER_2" listIdentities
-run_main -u "$NUMBER_2" trust "$NUMBER_1" -a
+run_main -a "$NUMBER_1" listIdentities
+run_main -a "$NUMBER_2" listIdentities
+run_main -a "$NUMBER_2" trust "$NUMBER_1" -a
## Basic send/receive
for OUTPUT in "plain-text" "json"; do
- run_main -u "$NUMBER_1" --output="$OUTPUT" getUserStatus "$NUMBER_1" "$NUMBER_2" "+111111111"
- run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
- run_main -u "$NUMBER_2" send "$NUMBER_1" -m hi
- run_main -u "$NUMBER_1" send -g "$GROUP_ID" -m hi -a LICENSE
- TIMESTAMP=$(uname -a | run_main -u "$NUMBER_1" send "$NUMBER_2")
- run_main -u "$NUMBER_2" sendReaction "$NUMBER_1" -e 🍀 -a "$NUMBER_1" -t "$TIMESTAMP"
- run_main -u "$NUMBER_1" remoteDelete "$NUMBER_2" -t "$TIMESTAMP"
- run_main -u "$NUMBER_2" --output="$OUTPUT" receive
- run_main -u "$NUMBER_1" --output="$OUTPUT" receive
- run_main -u "$NUMBER_1" send -e "$NUMBER_2"
- run_main -u "$NUMBER_2" --output="$OUTPUT" receive
+ run_main -a "$NUMBER_1" --output="$OUTPUT" getUserStatus "$NUMBER_1" "$NUMBER_2" "+111111111"
+ run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
+ run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi
+ run_main -a "$NUMBER_1" send -g "$GROUP_ID" -m hi -a LICENSE
+ TIMESTAMP=$(uname -a | run_main -a "$NUMBER_1" send "$NUMBER_2")
+ run_main -a "$NUMBER_2" sendReaction "$NUMBER_1" -e 🍀 -a "$NUMBER_1" -t "$TIMESTAMP"
+ run_main -a "$NUMBER_1" remoteDelete "$NUMBER_2" -t "$TIMESTAMP"
+ run_main -a "$NUMBER_2" --output="$OUTPUT" receive
+ run_main -a "$NUMBER_1" --output="$OUTPUT" receive
+ run_main -a "$NUMBER_1" send -e "$NUMBER_2"
+ run_main -a "$NUMBER_2" --output="$OUTPUT" receive
done
## Profile
-run_main -u "$NUMBER_1" updateProfile --given-name=GIVEN --family-name=FAMILY --about=ABOUT --about-emoji=EMOJI --avatar=LICENSE
+run_main -a "$NUMBER_1" updateProfile --given-name=GIVEN --family-name=FAMILY --about=ABOUT --about-emoji=EMOJI --avatar=LICENSE
## Provisioning
link "$NUMBER_1"
link "$NUMBER_2"
-run_main -u "$NUMBER_1" listDevices
-run_linked -u "$NUMBER_1" sendSyncRequest
-run_main -u "$NUMBER_1" sendContacts
+run_main -a "$NUMBER_1" listDevices
+run_linked -a "$NUMBER_1" sendSyncRequest
+run_main -a "$NUMBER_1" sendContacts
for OUTPUT in "plain-text" "json"; do
- run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
- run_main -u "$NUMBER_2" send "$NUMBER_1" -m hi
- run_main -u "$NUMBER_2" --output="$OUTPUT" receive
- run_main -u "$NUMBER_1" --output="$OUTPUT" receive
- run_linked -u "$NUMBER_1" --output="$OUTPUT" receive
+ run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
+ run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi
+ run_main -a "$NUMBER_2" --output="$OUTPUT" receive
+ run_main -a "$NUMBER_1" --output="$OUTPUT" receive
+ run_linked -a "$NUMBER_1" --output="$OUTPUT" receive
done
-run_main -u "$NUMBER_1" removeDevice -d 2
+run_main -a "$NUMBER_1" removeDevice -d 2
## Unregister
-run_main -u "$NUMBER_1" unregister
-run_main -u "$NUMBER_2" unregister --delete-account
+run_main -a "$NUMBER_1" unregister
+run_main -a "$NUMBER_2" unregister --delete-account
return getObjectPath(null);
}
- public static String getObjectPath(String username) {
- if (username == null) {
+ public static String getObjectPath(String account) {
+ if (account == null) {
return SIGNAL_OBJECT_BASE_PATH;
}
- return SIGNAL_OBJECT_BASE_PATH + "/" + username.replace('+', '_');
+ return SIGNAL_OBJECT_BASE_PATH + "/" + account.replace('+', '_');
}
}
"The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
final var recipientName = e.getSender().getLegacyIdentifier();
writer.println(
- "Use 'signal-cli -u {} listIdentities -n {}', verify the key and run 'signal-cli -u {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
+ "Use 'signal-cli -a {} listIdentities -n {}', verify the key and run 'signal-cli -a {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
m.getSelfNumber(),
recipientName,
m.getSelfNumber(),
recipientName);
writer.println(
- "If you don't care about security, use 'signal-cli -u {} trust -a {}' to trust it without verification",
+ "If you don't care about security, use 'signal-cli -a {} trust -a {}' to trust it without verification",
m.getSelfNumber(),
recipientName);
} else {
) throws Error.Failure, Error.InvalidNumber {
if (!Manager.isValidNumber(number, null)) {
throw new SignalControl.Error.InvalidNumber(
- "Invalid username (phone number), make sure you include the country code.");
+ "Invalid account (phone number), make sure you include the country code.");
}
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
registrationManager.register(voiceVerification, captcha);