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