]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
03c44013eedd2cc22c4637beab9761729f910c46
[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, String accountPath
89 ) throws IOException, NotRegisteredException, AccountCheckException {
90 if (accountPath == null) {
91 throw new NotRegisteredException();
92 }
93 if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
94 throw new NotRegisteredException();
95 }
96
97 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
98 if (!number.equals(account.getNumber())) {
99 account.close();
100 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
101 }
102
103 if (!account.isRegistered()) {
104 account.close();
105 throw new NotRegisteredException();
106 }
107
108 if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) {
109 throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
110 }
111
112 account.initDatabase();
113
114 final var manager = new ManagerImpl(account,
115 pathConfig,
116 new AccountFileUpdaterImpl(accountsStore, accountPath),
117 serviceEnvironmentConfig,
118 userAgent);
119
120 try {
121 manager.checkAccountState();
122 } catch (DeprecatedVersionException e) {
123 manager.close();
124 throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
125 } catch (IOException e) {
126 manager.close();
127 throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
128 }
129
130 if (account.getServiceEnvironment() == null) {
131 account.setServiceEnvironment(serviceEnvironment);
132 accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci());
133 }
134
135 return manager;
136 }
137
138 public ProvisioningManager initProvisioningManager() {
139 return initProvisioningManager(null);
140 }
141
142 public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
143 return new ProvisioningManagerImpl(pathConfig,
144 serviceEnvironmentConfig,
145 userAgent,
146 newManagerListener,
147 accountsStore);
148 }
149
150 public RegistrationManager initRegistrationManager(String number) throws IOException {
151 return initRegistrationManager(number, null);
152 }
153
154 public RegistrationManager initRegistrationManager(
155 String number, Consumer<Manager> newManagerListener
156 ) throws IOException {
157 final var accountPath = accountsStore.getPathByNumber(number);
158 if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
159 final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
160 var aciIdentityKey = KeyUtils.generateIdentityKeyPair();
161 var pniIdentityKey = KeyUtils.generateIdentityKeyPair();
162
163 var profileKey = KeyUtils.createProfileKey();
164 var account = SignalAccount.create(pathConfig.dataPath(),
165 newAccountPath,
166 number,
167 serviceEnvironment,
168 aciIdentityKey,
169 pniIdentityKey,
170 profileKey,
171 settings);
172 account.initDatabase();
173
174 return new RegistrationManagerImpl(account,
175 pathConfig,
176 serviceEnvironmentConfig,
177 userAgent,
178 newManagerListener,
179 new AccountFileUpdaterImpl(accountsStore, newAccountPath));
180 }
181
182 var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
183 if (!number.equals(account.getNumber())) {
184 account.close();
185 throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
186 }
187 account.initDatabase();
188
189 return new RegistrationManagerImpl(account,
190 pathConfig,
191 serviceEnvironmentConfig,
192 userAgent,
193 newManagerListener,
194 new AccountFileUpdaterImpl(accountsStore, accountPath));
195 }
196 }