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
.api
.Pair
;
6 import org
.asamk
.signal
.manager
.api
.ServiceEnvironment
;
7 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
8 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
9 import org
.asamk
.signal
.manager
.internal
.AccountFileUpdaterImpl
;
10 import org
.asamk
.signal
.manager
.internal
.ManagerImpl
;
11 import org
.asamk
.signal
.manager
.internal
.MultiAccountManagerImpl
;
12 import org
.asamk
.signal
.manager
.internal
.PathConfig
;
13 import org
.asamk
.signal
.manager
.internal
.ProvisioningManagerImpl
;
14 import org
.asamk
.signal
.manager
.internal
.RegistrationManagerImpl
;
15 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
16 import org
.asamk
.signal
.manager
.storage
.accounts
.AccountsStore
;
17 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
18 import org
.slf4j
.Logger
;
19 import org
.slf4j
.LoggerFactory
;
20 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.DeprecatedVersionException
;
23 import java
.io
.IOException
;
24 import java
.util
.Objects
;
26 import java
.util
.function
.Consumer
;
28 public class SignalAccountFiles
{
30 private static final Logger logger
= LoggerFactory
.getLogger(MultiAccountManager
.class);
32 private final PathConfig pathConfig
;
33 private final ServiceEnvironment serviceEnvironment
;
34 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
35 private final String userAgent
;
36 private final Settings settings
;
37 private final AccountsStore accountsStore
;
39 public SignalAccountFiles(
40 final File settingsPath
,
41 final ServiceEnvironment serviceEnvironment
,
42 final String userAgent
,
43 final Settings settings
44 ) throws IOException
{
45 this.pathConfig
= PathConfig
.createDefault(settingsPath
);
46 this.serviceEnvironment
= serviceEnvironment
;
47 this.serviceEnvironmentConfig
= ServiceConfig
.getServiceEnvironmentConfig(this.serviceEnvironment
, userAgent
);
48 this.userAgent
= userAgent
;
49 this.settings
= settings
;
50 this.accountsStore
= new AccountsStore(pathConfig
.dataPath(), serviceEnvironment
, accountPath
-> {
51 if (accountPath
== null || !SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)) {
56 return SignalAccount
.load(pathConfig
.dataPath(), accountPath
, false, settings
);
57 } catch (Exception e
) {
63 public Set
<String
> getAllLocalAccountNumbers() throws IOException
{
64 return accountsStore
.getAllNumbers();
67 public MultiAccountManager
initMultiAccountManager() throws IOException
, AccountCheckException
{
68 final var managerPairs
= accountsStore
.getAllAccounts().parallelStream().map(a
-> {
70 return new Pair
<Manager
, Throwable
>(initManager(a
.number(), a
.path()), null);
71 } catch (NotRegisteredException e
) {
72 logger
.warn("Ignoring {}: {} ({})", a
.number(), e
.getMessage(), e
.getClass().getSimpleName());
74 } catch (AccountCheckException
| IOException e
) {
75 logger
.error("Failed to load {}: {} ({})", a
.number(), e
.getMessage(), e
.getClass().getSimpleName());
76 return new Pair
<Manager
, Throwable
>(null, e
);
78 }).filter(Objects
::nonNull
).toList();
80 for (final var pair
: managerPairs
) {
81 if (pair
.second() instanceof IOException e
) {
83 } else if (pair
.second() instanceof AccountCheckException e
) {
88 final var managers
= managerPairs
.stream().map(Pair
::first
).toList();
89 return new MultiAccountManagerImpl(managers
, this);
92 public Manager
initManager(String number
) throws IOException
, NotRegisteredException
, AccountCheckException
{
93 final var accountPath
= accountsStore
.getPathByNumber(number
);
94 return this.initManager(number
, accountPath
);
97 private Manager
initManager(
100 ) throws IOException
, NotRegisteredException
, AccountCheckException
{
101 if (accountPath
== null) {
102 throw new NotRegisteredException();
104 if (!SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)) {
105 throw new NotRegisteredException();
108 var account
= SignalAccount
.load(pathConfig
.dataPath(), accountPath
, true, settings
);
109 if (!number
.equals(account
.getNumber())) {
111 throw new IOException("Number in account file doesn't match expected number: " + account
.getNumber());
114 if (!account
.isRegistered()) {
116 throw new NotRegisteredException();
119 if (account
.getServiceEnvironment() != null && account
.getServiceEnvironment() != serviceEnvironment
) {
120 throw new IOException("Account is registered in another environment: " + account
.getServiceEnvironment());
123 account
.initDatabase();
125 final var manager
= new ManagerImpl(account
,
127 new AccountFileUpdaterImpl(accountsStore
, accountPath
),
128 serviceEnvironmentConfig
,
132 manager
.checkAccountState();
133 } catch (DeprecatedVersionException e
) {
135 throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
136 } catch (IOException e
) {
138 throw new AccountCheckException("Error while checking account " + number
+ ": " + e
.getMessage(), e
);
141 if (account
.getServiceEnvironment() == null) {
142 account
.setServiceEnvironment(serviceEnvironment
);
143 accountsStore
.updateAccount(accountPath
, account
.getNumber(), account
.getAci());
149 public ProvisioningManager
initProvisioningManager() {
150 return initProvisioningManager(null);
153 public ProvisioningManager
initProvisioningManager(Consumer
<Manager
> newManagerListener
) {
154 return new ProvisioningManagerImpl(pathConfig
,
155 serviceEnvironmentConfig
,
161 public RegistrationManager
initRegistrationManager(String number
) throws IOException
{
162 return initRegistrationManager(number
, null);
165 public RegistrationManager
initRegistrationManager(
167 Consumer
<Manager
> newManagerListener
168 ) throws IOException
{
169 final var accountPath
= accountsStore
.getPathByNumber(number
);
170 if (accountPath
== null || !SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)) {
171 final var newAccountPath
= accountPath
== null ? accountsStore
.addAccount(number
, null) : accountPath
;
172 var aciIdentityKey
= KeyUtils
.generateIdentityKeyPair();
173 var pniIdentityKey
= KeyUtils
.generateIdentityKeyPair();
175 var profileKey
= KeyUtils
.createProfileKey();
176 var account
= SignalAccount
.create(pathConfig
.dataPath(),
184 account
.initDatabase();
186 return new RegistrationManagerImpl(account
,
188 serviceEnvironmentConfig
,
191 new AccountFileUpdaterImpl(accountsStore
, newAccountPath
));
194 var account
= SignalAccount
.load(pathConfig
.dataPath(), accountPath
, true, settings
);
195 if (!number
.equals(account
.getNumber())) {
197 throw new IOException("Number in account file doesn't match expected number: " + account
.getNumber());
199 account
.initDatabase();
201 return new RegistrationManagerImpl(account
,
203 serviceEnvironmentConfig
,
206 new AccountFileUpdaterImpl(accountsStore
, accountPath
));