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