]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
Fix NPR when loading an inactive group
[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.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.Objects;
24 import java.util.Set;
25 import java.util.function.Consumer;
26
27 public class SignalAccountFiles {
28
29 private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class);
30
31 private final PathConfig pathConfig;
32 private final ServiceEnvironment serviceEnvironment;
33 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
34 private final String userAgent;
35 private final Settings settings;
36 private final AccountsStore accountsStore;
37
38 public SignalAccountFiles(
39 final File settingsPath,
40 final ServiceEnvironment serviceEnvironment,
41 final String userAgent,
42 final Settings settings
43 ) throws IOException {
44 this.pathConfig = PathConfig.createDefault(settingsPath);
45 this.serviceEnvironment = serviceEnvironment;
46 this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent);
47 this.userAgent = userAgent;
48 this.settings = settings;
49 this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> {
50 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
51 return null;
52 }
53
54 try {
55 return SignalAccount.load(pathConfig.dataPath(), accountPath, false, settings);
56 } catch (Exception e) {
57 return null;
58 }
59 });
60 }
61
62 public Set<String> getAllLocalAccountNumbers() throws IOException {
63 return accountsStore.getAllNumbers();
64 }
65
66 public MultiAccountManager initMultiAccountManager() throws IOException {
67 final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> {
68 try {
69 return initManager(a.number(), a.path());
70 } catch (NotRegisteredException | IOException | AccountCheckException e) {
71 logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
72 return null;
73 } catch (Throwable e) {
74 logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
75 throw e;
76 }
77 }).filter(Objects::nonNull).toList();
78
79 return new MultiAccountManagerImpl(managers, this);
80 }
81
82 public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
83 final var accountPath = accountsStore.getPathByNumber(number);
84 return this.initManager(number, accountPath);
85 }
86
87 private Manager initManager(
88 String number,
89 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,
157 Consumer<Manager> newManagerListener
158 ) throws IOException {
159 final var accountPath = accountsStore.getPathByNumber(number);
160 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
161 final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
162 var aciIdentityKey = KeyUtils.generateIdentityKeyPair();
163 var pniIdentityKey = KeyUtils.generateIdentityKeyPair();
164
165 var profileKey = KeyUtils.createProfileKey();
166 var account = SignalAccount.create(pathConfig.dataPath(),
167 newAccountPath,
168 number,
169 serviceEnvironment,
170 aciIdentityKey,
171 pniIdentityKey,
172 profileKey,
173 settings);
174 account.initDatabase();
175
176 return new RegistrationManagerImpl(account,
177 pathConfig,
178 serviceEnvironmentConfig,
179 userAgent,
180 newManagerListener,
181 new AccountFileUpdaterImpl(accountsStore, newAccountPath));
182 }
183
184 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
185 if (!number.equals(account.getNumber())) {
186 account.close();
187 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
188 }
189 account.initDatabase();
190
191 return new RegistrationManagerImpl(account,
192 pathConfig,
193 serviceEnvironmentConfig,
194 userAgent,
195 newManagerListener,
196 new AccountFileUpdaterImpl(accountsStore, accountPath));
197 }
198 }