]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
305b061d53be124d0d288aac0b8cd7dbf4aa1413
[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 } catch (Throwable e) {
69 logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
70 throw e;
71 }
72 }).filter(Objects::nonNull).toList();
73
74 return new MultiAccountManagerImpl(managers, this);
75 }
76
77 public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
78 final var accountPath = accountsStore.getPathByNumber(number);
79 return this.initManager(number, accountPath);
80 }
81
82 private Manager initManager(
83 String number, String accountPath
84 ) throws IOException, NotRegisteredException, AccountCheckException {
85 if (accountPath == null) {
86 throw new NotRegisteredException();
87 }
88 if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
89 throw new NotRegisteredException();
90 }
91
92 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
93 if (!number.equals(account.getNumber())) {
94 account.close();
95 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
96 }
97
98 if (!account.isRegistered()) {
99 account.close();
100 throw new NotRegisteredException();
101 }
102
103 if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) {
104 throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
105 }
106
107 account.initDatabase();
108
109 final var manager = new ManagerImpl(account,
110 pathConfig,
111 new AccountFileUpdaterImpl(accountsStore, accountPath),
112 serviceEnvironmentConfig,
113 userAgent);
114
115 try {
116 manager.checkAccountState();
117 } catch (IOException e) {
118 manager.close();
119 throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
120 }
121
122 if (account.getServiceEnvironment() == null) {
123 account.setServiceEnvironment(serviceEnvironment);
124 accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci());
125 }
126
127 return manager;
128 }
129
130 public ProvisioningManager initProvisioningManager() {
131 return initProvisioningManager(null);
132 }
133
134 public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
135 return new ProvisioningManagerImpl(pathConfig,
136 serviceEnvironmentConfig,
137 userAgent,
138 newManagerListener,
139 accountsStore);
140 }
141
142 public RegistrationManager initRegistrationManager(String number) throws IOException {
143 return initRegistrationManager(number, null);
144 }
145
146 public RegistrationManager initRegistrationManager(
147 String number, Consumer<Manager> newManagerListener
148 ) throws IOException {
149 final var accountPath = accountsStore.getPathByNumber(number);
150 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
151 final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
152 var aciIdentityKey = KeyUtils.generateIdentityKeyPair();
153 var pniIdentityKey = KeyUtils.generateIdentityKeyPair();
154 var registrationId = KeyHelper.generateRegistrationId(false);
155 var pniRegistrationId = KeyHelper.generateRegistrationId(false);
156
157 var profileKey = KeyUtils.createProfileKey();
158 var account = SignalAccount.create(pathConfig.dataPath(),
159 newAccountPath,
160 number,
161 serviceEnvironment,
162 aciIdentityKey,
163 pniIdentityKey,
164 registrationId,
165 pniRegistrationId,
166 profileKey,
167 trustNewIdentity);
168
169 return new RegistrationManagerImpl(account,
170 pathConfig,
171 serviceEnvironmentConfig,
172 userAgent,
173 newManagerListener,
174 new AccountFileUpdaterImpl(accountsStore, newAccountPath));
175 }
176
177 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
178 if (!number.equals(account.getNumber())) {
179 account.close();
180 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
181 }
182
183 return new RegistrationManagerImpl(account,
184 pathConfig,
185 serviceEnvironmentConfig,
186 userAgent,
187 newManagerListener,
188 new AccountFileUpdaterImpl(accountsStore, accountPath));
189 }
190 }