]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
f397b5348e769b2985723eddc6f38b405e9fc586
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / SignalAccountFiles.java
1 package org.asamk.signal.manager;
2
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
16 import java.io.File;
17 import java.io.IOException;
18 import java.util.Objects;
19 import java.util.Set;
20 import java.util.function.Consumer;
21
22 public class SignalAccountFiles {
23
24 private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class);
25
26 private final PathConfig pathConfig;
27 private final ServiceEnvironment serviceEnvironment;
28 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
29 private final String userAgent;
30 private final TrustNewIdentity trustNewIdentity;
31 private final AccountsStore accountsStore;
32
33 public SignalAccountFiles(
34 final File settingsPath,
35 final ServiceEnvironment serviceEnvironment,
36 final String userAgent,
37 final TrustNewIdentity trustNewIdentity
38 ) throws IOException {
39 this.pathConfig = PathConfig.createDefault(settingsPath);
40 this.serviceEnvironment = serviceEnvironment;
41 this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent);
42 this.userAgent = userAgent;
43 this.trustNewIdentity = trustNewIdentity;
44 this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> {
45 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
46 return null;
47 }
48
49 try {
50 return SignalAccount.load(pathConfig.dataPath(), accountPath, false, trustNewIdentity);
51 } catch (Exception e) {
52 return null;
53 }
54 });
55 }
56
57 public Set<String> getAllLocalAccountNumbers() throws IOException {
58 return accountsStore.getAllNumbers();
59 }
60
61 public MultiAccountManager initMultiAccountManager() throws IOException {
62 final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> {
63 try {
64 return initManager(a.number(), a.path());
65 } catch (NotRegisteredException | IOException | AccountCheckException e) {
66 logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
67 return null;
68 }
69 }).filter(Objects::nonNull).toList();
70
71 return new MultiAccountManagerImpl(managers, this);
72 }
73
74 public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
75 final var accountPath = accountsStore.getPathByNumber(number);
76 return this.initManager(number, accountPath);
77 }
78
79 private Manager initManager(
80 String number, String accountPath
81 ) throws IOException, NotRegisteredException, AccountCheckException {
82 if (accountPath == null) {
83 throw new NotRegisteredException();
84 }
85 if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
86 throw new NotRegisteredException();
87 }
88
89 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
90 if (!number.equals(account.getNumber())) {
91 account.close();
92 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
93 }
94
95 if (!account.isRegistered()) {
96 account.close();
97 throw new NotRegisteredException();
98 }
99
100 if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) {
101 throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
102 }
103
104 account.initDatabase();
105
106 final var manager = new ManagerImpl(account,
107 pathConfig,
108 new AccountFileUpdaterImpl(accountsStore, accountPath),
109 serviceEnvironmentConfig,
110 userAgent);
111
112 try {
113 manager.checkAccountState();
114 } catch (IOException e) {
115 manager.close();
116 throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
117 }
118
119 if (account.getServiceEnvironment() == null) {
120 account.setServiceEnvironment(serviceEnvironment);
121 accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci());
122 }
123
124 return manager;
125 }
126
127 public ProvisioningManager initProvisioningManager() {
128 return initProvisioningManager(null);
129 }
130
131 public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
132 return new ProvisioningManagerImpl(pathConfig,
133 serviceEnvironmentConfig,
134 userAgent,
135 newManagerListener,
136 accountsStore);
137 }
138
139 public RegistrationManager initRegistrationManager(String number) throws IOException {
140 return initRegistrationManager(number, null);
141 }
142
143 public RegistrationManager initRegistrationManager(
144 String number, Consumer<Manager> newManagerListener
145 ) throws IOException {
146 final var accountPath = accountsStore.getPathByNumber(number);
147 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
148 final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
149 var aciIdentityKey = KeyUtils.generateIdentityKeyPair();
150 var pniIdentityKey = KeyUtils.generateIdentityKeyPair();
151 var registrationId = KeyHelper.generateRegistrationId(false);
152
153 var profileKey = KeyUtils.createProfileKey();
154 var account = SignalAccount.create(pathConfig.dataPath(),
155 newAccountPath,
156 number,
157 serviceEnvironment,
158 aciIdentityKey,
159 pniIdentityKey,
160 registrationId,
161 profileKey,
162 trustNewIdentity);
163
164 return new RegistrationManagerImpl(account,
165 pathConfig,
166 serviceEnvironmentConfig,
167 userAgent,
168 newManagerListener,
169 new AccountFileUpdaterImpl(accountsStore, newAccountPath));
170 }
171
172 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
173 if (!number.equals(account.getNumber())) {
174 account.close();
175 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
176 }
177
178 return new RegistrationManagerImpl(account,
179 pathConfig,
180 serviceEnvironmentConfig,
181 userAgent,
182 newManagerListener,
183 new AccountFileUpdaterImpl(accountsStore, accountPath));
184 }
185 }