]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
Exit if account check fails at startup
[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.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;
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, AccountCheckException {
68 final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> {
69 try {
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());
73 return null;
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);
77 }
78 }).filter(Objects::nonNull).toList();
79
80 for (final var pair : managerPairs) {
81 if (pair.second() instanceof IOException e) {
82 throw e;
83 } else if (pair.second() instanceof AccountCheckException e) {
84 throw e;
85 }
86 }
87
88 final var managers = managerPairs.stream().map(Pair::first).toList();
89 return new MultiAccountManagerImpl(managers, this);
90 }
91
92 public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
93 final var accountPath = accountsStore.getPathByNumber(number);
94 return this.initManager(number, accountPath);
95 }
96
97 private Manager initManager(
98 String number,
99 String accountPath
100 ) throws IOException, NotRegisteredException, AccountCheckException {
101 if (accountPath == null) {
102 throw new NotRegisteredException();
103 }
104 if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
105 throw new NotRegisteredException();
106 }
107
108 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
109 if (!number.equals(account.getNumber())) {
110 account.close();
111 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
112 }
113
114 if (!account.isRegistered()) {
115 account.close();
116 throw new NotRegisteredException();
117 }
118
119 if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) {
120 throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
121 }
122
123 account.initDatabase();
124
125 final var manager = new ManagerImpl(account,
126 pathConfig,
127 new AccountFileUpdaterImpl(accountsStore, accountPath),
128 serviceEnvironmentConfig,
129 userAgent);
130
131 try {
132 manager.checkAccountState();
133 } catch (DeprecatedVersionException e) {
134 manager.close();
135 throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
136 } catch (IOException e) {
137 manager.close();
138 throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
139 }
140
141 if (account.getServiceEnvironment() == null) {
142 account.setServiceEnvironment(serviceEnvironment);
143 accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci());
144 }
145
146 return manager;
147 }
148
149 public ProvisioningManager initProvisioningManager() {
150 return initProvisioningManager(null);
151 }
152
153 public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
154 return new ProvisioningManagerImpl(pathConfig,
155 serviceEnvironmentConfig,
156 userAgent,
157 newManagerListener,
158 accountsStore);
159 }
160
161 public RegistrationManager initRegistrationManager(String number) throws IOException {
162 return initRegistrationManager(number, null);
163 }
164
165 public RegistrationManager initRegistrationManager(
166 String number,
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();
174
175 var profileKey = KeyUtils.createProfileKey();
176 var account = SignalAccount.create(pathConfig.dataPath(),
177 newAccountPath,
178 number,
179 serviceEnvironment,
180 aciIdentityKey,
181 pniIdentityKey,
182 profileKey,
183 settings);
184 account.initDatabase();
185
186 return new RegistrationManagerImpl(account,
187 pathConfig,
188 serviceEnvironmentConfig,
189 userAgent,
190 newManagerListener,
191 new AccountFileUpdaterImpl(accountsStore, newAccountPath));
192 }
193
194 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
195 if (!number.equals(account.getNumber())) {
196 account.close();
197 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
198 }
199 account.initDatabase();
200
201 return new RegistrationManagerImpl(account,
202 pathConfig,
203 serviceEnvironmentConfig,
204 userAgent,
205 newManagerListener,
206 new AccountFileUpdaterImpl(accountsStore, accountPath));
207 }
208 }