]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
Add pre key cleanup and improve refresh
[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.api.ServiceEnvironment;
6 import org.asamk.signal.manager.config.ServiceConfig;
7 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
8 import org.asamk.signal.manager.internal.AccountFileUpdaterImpl;
9 import org.asamk.signal.manager.internal.ManagerImpl;
10 import org.asamk.signal.manager.internal.MultiAccountManagerImpl;
11 import org.asamk.signal.manager.internal.PathConfig;
12 import org.asamk.signal.manager.internal.ProvisioningManagerImpl;
13 import org.asamk.signal.manager.internal.RegistrationManagerImpl;
14 import org.asamk.signal.manager.storage.SignalAccount;
15 import org.asamk.signal.manager.storage.accounts.AccountsStore;
16 import org.asamk.signal.manager.util.KeyUtils;
17 import org.signal.libsignal.protocol.util.KeyHelper;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.Objects;
25 import java.util.Set;
26 import java.util.function.Consumer;
27
28 public class SignalAccountFiles {
29
30 private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class);
31
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;
38
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)) {
52 return null;
53 }
54
55 try {
56 return SignalAccount.load(pathConfig.dataPath(), accountPath, false, settings);
57 } catch (Exception e) {
58 return null;
59 }
60 });
61 }
62
63 public Set<String> getAllLocalAccountNumbers() throws IOException {
64 return accountsStore.getAllNumbers();
65 }
66
67 public MultiAccountManager initMultiAccountManager() throws IOException {
68 final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> {
69 try {
70 return initManager(a.number(), a.path());
71 } catch (NotRegisteredException | IOException | AccountCheckException e) {
72 logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
73 return null;
74 } catch (Throwable e) {
75 logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
76 throw e;
77 }
78 }).filter(Objects::nonNull).toList();
79
80 return new MultiAccountManagerImpl(managers, this);
81 }
82
83 public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
84 final var accountPath = accountsStore.getPathByNumber(number);
85 return this.initManager(number, accountPath);
86 }
87
88 private Manager initManager(
89 String number, String accountPath
90 ) throws IOException, NotRegisteredException, AccountCheckException {
91 if (accountPath == null) {
92 throw new NotRegisteredException();
93 }
94 if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
95 throw new NotRegisteredException();
96 }
97
98 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
99 if (!number.equals(account.getNumber())) {
100 account.close();
101 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
102 }
103
104 if (!account.isRegistered()) {
105 account.close();
106 throw new NotRegisteredException();
107 }
108
109 if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) {
110 throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
111 }
112
113 account.initDatabase();
114
115 final var manager = new ManagerImpl(account,
116 pathConfig,
117 new AccountFileUpdaterImpl(accountsStore, accountPath),
118 serviceEnvironmentConfig,
119 userAgent);
120
121 try {
122 manager.checkAccountState();
123 } catch (DeprecatedVersionException e) {
124 manager.close();
125 throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
126 } catch (IOException e) {
127 manager.close();
128 throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
129 }
130
131 if (account.getServiceEnvironment() == null) {
132 account.setServiceEnvironment(serviceEnvironment);
133 accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci());
134 }
135
136 return manager;
137 }
138
139 public ProvisioningManager initProvisioningManager() {
140 return initProvisioningManager(null);
141 }
142
143 public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
144 return new ProvisioningManagerImpl(pathConfig,
145 serviceEnvironmentConfig,
146 userAgent,
147 newManagerListener,
148 accountsStore);
149 }
150
151 public RegistrationManager initRegistrationManager(String number) throws IOException {
152 return initRegistrationManager(number, null);
153 }
154
155 public RegistrationManager initRegistrationManager(
156 String number, Consumer<Manager> newManagerListener
157 ) throws IOException {
158 final var accountPath = accountsStore.getPathByNumber(number);
159 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
160 final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
161 var aciIdentityKey = KeyUtils.generateIdentityKeyPair();
162 var pniIdentityKey = KeyUtils.generateIdentityKeyPair();
163 var registrationId = KeyHelper.generateRegistrationId(false);
164 var pniRegistrationId = KeyHelper.generateRegistrationId(false);
165
166 var profileKey = KeyUtils.createProfileKey();
167 var account = SignalAccount.create(pathConfig.dataPath(),
168 newAccountPath,
169 number,
170 serviceEnvironment,
171 aciIdentityKey,
172 pniIdentityKey,
173 registrationId,
174 pniRegistrationId,
175 profileKey,
176 settings);
177
178 return new RegistrationManagerImpl(account,
179 pathConfig,
180 serviceEnvironmentConfig,
181 userAgent,
182 newManagerListener,
183 new AccountFileUpdaterImpl(accountsStore, newAccountPath));
184 }
185
186 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
187 if (!number.equals(account.getNumber())) {
188 account.close();
189 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
190 }
191
192 return new RegistrationManagerImpl(account,
193 pathConfig,
194 serviceEnvironmentConfig,
195 userAgent,
196 newManagerListener,
197 new AccountFileUpdaterImpl(accountsStore, accountPath));
198 }
199 }