X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/d89e93ad473cc1c6dd5f4dc615b7d0c5721e3dc2..4730e9cbc7e440b8ca975fa4713a6e15fe0597c7:/src/main/java/org/asamk/signal/Manager.java diff --git a/src/main/java/org/asamk/signal/Manager.java b/src/main/java/org/asamk/signal/Manager.java index e5214302..b20ca002 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -16,6 +16,10 @@ */ package org.asamk.signal; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonGenerator; @@ -25,9 +29,67 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.apache.http.util.TextUtils; import org.asamk.Signal; -import org.whispersystems.libsignal.*; +import org.asamk.signal.storage.contacts.ContactInfo; +import org.asamk.signal.storage.contacts.JsonContactsStore; +import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.storage.groups.JsonGroupStore; +import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; +import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; +import org.asamk.signal.storage.threads.JsonThreadStore; +import org.asamk.signal.storage.threads.ThreadInfo; +import org.asamk.signal.util.Base64; +import org.asamk.signal.util.Util; +import org.whispersystems.libsignal.DuplicateMessageException; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.IdentityKeyPair; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.InvalidKeyIdException; +import org.whispersystems.libsignal.InvalidMessageException; +import org.whispersystems.libsignal.InvalidVersionException; +import org.whispersystems.libsignal.LegacyMessageException; +import org.whispersystems.libsignal.NoSessionException; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECKeyPair; import org.whispersystems.libsignal.ecc.ECPublicKey; @@ -44,39 +106,39 @@ import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.api.messages.*; -import org.whispersystems.signalservice.api.messages.multidevice.*; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; +import org.whispersystems.signalservice.api.messages.SignalServiceContent; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; +import org.whispersystems.signalservice.api.messages.SignalServiceGroup; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; +import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.TrustStore; -import org.whispersystems.signalservice.api.push.exceptions.*; +import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; +import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; +import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; - -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static java.nio.file.attribute.PosixFilePermission.*; +import org.whispersystems.signalservice.internal.push.SignalServiceUrl; class Manager implements Signal { private final static String URL = "https://textsecure-service.whispersystems.org"; private final static TrustStore TRUST_STORE = new WhisperTrustStore(); + private final static SignalServiceUrl[] serviceUrls = new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}; public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle(); public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion(); @@ -108,6 +170,7 @@ class Manager implements Signal { private JsonGroupStore groupStore; private JsonContactsStore contactStore; private JsonThreadStore threadStore; + private SignalServiceMessagePipe messagePipe = null; public Manager(String username, String settingsPath) { this.username = username; @@ -217,7 +280,7 @@ class Manager implements Signal { migrateLegacyConfigs(); - accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, deviceId, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceUrls, username, password, deviceId, USER_AGENT); try { if (registered && accountManager.getPreKeysCount() < PREKEY_MINIMUM_COUNT) { refreshPreKeys(); @@ -342,7 +405,7 @@ class Manager implements Signal { public void register(boolean voiceVerification) throws IOException { password = Util.getSecret(18); - accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceUrls, username, password, USER_AGENT); if (voiceVerification) accountManager.requestVoiceVerificationCode(); @@ -353,10 +416,21 @@ class Manager implements Signal { save(); } + public void updateAccountAttributes() throws IOException { + accountManager.setAccountAttributes(signalingKey, signalProtocolStore.getLocalRegistrationId(), false, false,true); + } + + public void unregister() throws IOException { + // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false. + // If this is the master device, other users can't send messages to this number anymore. + // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore. + accountManager.setGcmId(Optional.absent()); + } + public URI getDeviceLinkUri() throws TimeoutException, IOException { password = Util.getSecret(18); - accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceUrls, username, password, USER_AGENT); String uuid = accountManager.getNewDeviceUuid(); registered = false; @@ -493,7 +567,7 @@ class Manager implements Signal { public void verifyAccount(String verificationCode) throws IOException { verificationCode = verificationCode.replace("-", ""); signalingKey = Util.getSecret(52); - accountManager.verifyAccountWithCode(verificationCode, signalingKey, signalProtocolStore.getLocalRegistrationId(), false, true); + accountManager.verifyAccountWithCode(verificationCode, signalingKey, signalProtocolStore.getLocalRegistrationId(), false, false,true); //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); registered = true; @@ -566,6 +640,10 @@ class Manager implements Signal { } throw new NotAGroupMemberException(groupId, g.name); } + + public List getGroups() { + return groupStore.getGroups(); + } @Override public void sendGroupMessage(String messageText, List attachments, @@ -761,6 +839,65 @@ class Manager implements Signal { sendMessage(messageBuilder, recipients); } + @Override + public String getContactName(String number) { + ContactInfo contact = contactStore.getContact(number); + if (contact == null) { + return ""; + } else { + return contact.name; + } + } + + @Override + public void setContactName(String number, String name) { + ContactInfo contact = contactStore.getContact(number); + if (contact == null) { + contact = new ContactInfo(); + contact.number = number; + System.out.println("Add contact " + number + " named " + name); + } else { + System.out.println("Updating contact " + number + " name " + contact.name + " -> " + name); + } + contact.name = name; + contactStore.updateContact(contact); + save(); + } + + @Override + public String getGroupName(byte[] groupId) { + GroupInfo group = getGroup(groupId); + if (group == null) { + return ""; + } else { + return group.name; + } + } + + @Override + public List getGroupMembers(byte[] groupId) { + GroupInfo group = getGroup(groupId); + if (group == null) { + return new ArrayList(); + } else { + return new ArrayList(group.members); + } + } + + @Override + public void updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException { + if (name.isEmpty()) { + name = null; + } + if (members.size() == 0) { + members = null; + } + if (avatar.isEmpty()) { + avatar = null; + } + sendUpdateGroupMessage(groupId, name, members, avatar); + } + private void requestSyncGroups() throws IOException { SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build(); SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); @@ -783,8 +920,8 @@ class Manager implements Signal { private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { - SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, - deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceUrls, username, password, + deviceId, signalProtocolStore, USER_AGENT, Optional.fromNullable(messagePipe), Optional.absent()); try { messageSender.sendMessage(message); } catch (UntrustedIdentityException e) { @@ -800,8 +937,8 @@ class Manager implements Signal { SignalServiceDataMessage message = null; try { - SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, - deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceUrls, username, password, + deviceId, signalProtocolStore, USER_AGENT, Optional.fromNullable(messagePipe), Optional.absent()); message = messageBuilder.build(); if (message.getGroupInfo().isPresent()) { @@ -1029,11 +1166,12 @@ class Manager implements Signal { public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { retryFailedReceivedMessages(handler, ignoreAttachments); - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); - SignalServiceMessagePipe messagePipe = null; + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); try { - messagePipe = messageReceiver.createMessagePipe(); + if (messagePipe == null) { + messagePipe = messageReceiver.createMessagePipe(); + } while (true) { SignalServiceEnvelope envelope; @@ -1082,8 +1220,10 @@ class Manager implements Signal { } } } finally { - if (messagePipe != null) + if (messagePipe != null) { messagePipe.shutdown(); + messagePipe = null; + } } } @@ -1314,7 +1454,7 @@ class Manager implements Signal { } } - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); File tmpFile = Util.createTempFile(); try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile)) { @@ -1340,7 +1480,7 @@ class Manager implements Signal { } private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException { - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); return messageReceiver.retrieveAttachment(pointer, tmpFile); } @@ -1452,11 +1592,11 @@ class Manager implements Signal { return false; } for (JsonIdentityKeyStore.Identity id : ids) { - if (!Arrays.equals(id.identityKey.serialize(), fingerprint)) { + if (!Arrays.equals(id.getIdentityKey().serialize(), fingerprint)) { continue; } - signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_VERIFIED); + signalProtocolStore.saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); save(); return true; } @@ -1475,11 +1615,11 @@ class Manager implements Signal { return false; } for (JsonIdentityKeyStore.Identity id : ids) { - if (!safetyNumber.equals(computeSafetyNumber(name, id.identityKey))) { + if (!safetyNumber.equals(computeSafetyNumber(name, id.getIdentityKey()))) { continue; } - signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_VERIFIED); + signalProtocolStore.saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); save(); return true; } @@ -1497,8 +1637,8 @@ class Manager implements Signal { return false; } for (JsonIdentityKeyStore.Identity id : ids) { - if (id.trustLevel == TrustLevel.UNTRUSTED) { - signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_UNVERIFIED); + if (id.getTrustLevel() == TrustLevel.UNTRUSTED) { + signalProtocolStore.saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); } } save();