1 package org
.asamk
.signal
.manager
;
3 import org
.asamk
.signal
.manager
.api
.AccountCheckException
;
4 import org
.asamk
.signal
.manager
.api
.NotRegisteredException
;
5 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
6 import org
.asamk
.signal
.manager
.config
.ServiceEnvironment
;
7 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
8 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
9 import org
.asamk
.signal
.manager
.storage
.accounts
.AccountsStore
;
10 import org
.asamk
.signal
.manager
.storage
.identities
.TrustNewIdentity
;
11 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
12 import org
.signal
.libsignal
.protocol
.util
.KeyHelper
;
13 import org
.slf4j
.Logger
;
14 import org
.slf4j
.LoggerFactory
;
15 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.DeprecatedVersionException
;
18 import java
.io
.IOException
;
19 import java
.util
.Objects
;
21 import java
.util
.function
.Consumer
;
23 public class SignalAccountFiles
{
25 private static final Logger logger
= LoggerFactory
.getLogger(MultiAccountManager
.class);
27 private final PathConfig pathConfig
;
28 private final ServiceEnvironment serviceEnvironment
;
29 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
30 private final String userAgent
;
31 private final TrustNewIdentity trustNewIdentity
;
32 private final AccountsStore accountsStore
;
34 public SignalAccountFiles(
35 final File settingsPath
,
36 final ServiceEnvironment serviceEnvironment
,
37 final String userAgent
,
38 final TrustNewIdentity trustNewIdentity
39 ) throws IOException
{
40 this.pathConfig
= PathConfig
.createDefault(settingsPath
);
41 this.serviceEnvironment
= serviceEnvironment
;
42 this.serviceEnvironmentConfig
= ServiceConfig
.getServiceEnvironmentConfig(this.serviceEnvironment
, userAgent
);
43 this.userAgent
= userAgent
;
44 this.trustNewIdentity
= trustNewIdentity
;
45 this.accountsStore
= new AccountsStore(pathConfig
.dataPath(), serviceEnvironment
, accountPath
-> {
46 if (accountPath
== null || !SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)) {
51 return SignalAccount
.load(pathConfig
.dataPath(), accountPath
, false, trustNewIdentity
);
52 } catch (Exception e
) {
58 public Set
<String
> getAllLocalAccountNumbers() throws IOException
{
59 return accountsStore
.getAllNumbers();
62 public MultiAccountManager
initMultiAccountManager() throws IOException
{
63 final var managers
= accountsStore
.getAllAccounts().parallelStream().map(a
-> {
65 return initManager(a
.number(), a
.path());
66 } catch (NotRegisteredException
| IOException
| AccountCheckException e
) {
67 logger
.warn("Ignoring {}: {} ({})", a
.number(), e
.getMessage(), e
.getClass().getSimpleName());
69 } catch (Throwable e
) {
70 logger
.error("Failed to load {}: {} ({})", a
.number(), e
.getMessage(), e
.getClass().getSimpleName());
73 }).filter(Objects
::nonNull
).toList();
75 return new MultiAccountManagerImpl(managers
, this);
78 public Manager
initManager(String number
) throws IOException
, NotRegisteredException
, AccountCheckException
{
79 final var accountPath
= accountsStore
.getPathByNumber(number
);
80 return this.initManager(number
, accountPath
);
83 private Manager
initManager(
84 String number
, String accountPath
85 ) throws IOException
, NotRegisteredException
, AccountCheckException
{
86 if (accountPath
== null) {
87 throw new NotRegisteredException();
89 if (!SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)) {
90 throw new NotRegisteredException();
93 var account
= SignalAccount
.load(pathConfig
.dataPath(), accountPath
, true, trustNewIdentity
);
94 if (!number
.equals(account
.getNumber())) {
96 throw new IOException("Number in account file doesn't match expected number: " + account
.getNumber());
99 if (!account
.isRegistered()) {
101 throw new NotRegisteredException();
104 if (account
.getServiceEnvironment() != null && account
.getServiceEnvironment() != serviceEnvironment
) {
105 throw new IOException("Account is registered in another environment: " + account
.getServiceEnvironment());
108 account
.initDatabase();
110 final var manager
= new ManagerImpl(account
,
112 new AccountFileUpdaterImpl(accountsStore
, accountPath
),
113 serviceEnvironmentConfig
,
117 manager
.checkAccountState();
118 } catch (DeprecatedVersionException e
) {
120 throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
121 } catch (IOException e
) {
123 throw new AccountCheckException("Error while checking account " + number
+ ": " + e
.getMessage(), e
);
126 if (account
.getServiceEnvironment() == null) {
127 account
.setServiceEnvironment(serviceEnvironment
);
128 accountsStore
.updateAccount(accountPath
, account
.getNumber(), account
.getAci());
134 public ProvisioningManager
initProvisioningManager() {
135 return initProvisioningManager(null);
138 public ProvisioningManager
initProvisioningManager(Consumer
<Manager
> newManagerListener
) {
139 return new ProvisioningManagerImpl(pathConfig
,
140 serviceEnvironmentConfig
,
146 public RegistrationManager
initRegistrationManager(String number
) throws IOException
{
147 return initRegistrationManager(number
, null);
150 public RegistrationManager
initRegistrationManager(
151 String number
, Consumer
<Manager
> newManagerListener
152 ) throws IOException
{
153 final var accountPath
= accountsStore
.getPathByNumber(number
);
154 if (accountPath
== null || !SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)) {
155 final var newAccountPath
= accountPath
== null ? accountsStore
.addAccount(number
, null) : accountPath
;
156 var aciIdentityKey
= KeyUtils
.generateIdentityKeyPair();
157 var pniIdentityKey
= KeyUtils
.generateIdentityKeyPair();
158 var registrationId
= KeyHelper
.generateRegistrationId(false);
159 var pniRegistrationId
= KeyHelper
.generateRegistrationId(false);
161 var profileKey
= KeyUtils
.createProfileKey();
162 var account
= SignalAccount
.create(pathConfig
.dataPath(),
173 return new RegistrationManagerImpl(account
,
175 serviceEnvironmentConfig
,
178 new AccountFileUpdaterImpl(accountsStore
, newAccountPath
));
181 var account
= SignalAccount
.load(pathConfig
.dataPath(), accountPath
, true, trustNewIdentity
);
182 if (!number
.equals(account
.getNumber())) {
184 throw new IOException("Number in account file doesn't match expected number: " + account
.getNumber());
187 return new RegistrationManagerImpl(account
,
189 serviceEnvironmentConfig
,
192 new AccountFileUpdaterImpl(accountsStore
, accountPath
));