]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/Cli.java
Allow calling signal-cli without -u flag
[signal-cli] / src / main / java / org / asamk / signal / Cli.java
1 package org.asamk.signal;
2
3 import net.sourceforge.argparse4j.inf.Namespace;
4
5 import org.asamk.Signal;
6 import org.asamk.signal.commands.Command;
7 import org.asamk.signal.commands.Commands;
8 import org.asamk.signal.commands.DbusCommand;
9 import org.asamk.signal.commands.ExtendedDbusCommand;
10 import org.asamk.signal.commands.LocalCommand;
11 import org.asamk.signal.commands.MultiLocalCommand;
12 import org.asamk.signal.commands.ProvisioningCommand;
13 import org.asamk.signal.commands.RegistrationCommand;
14 import org.asamk.signal.manager.Manager;
15 import org.asamk.signal.manager.NotRegisteredException;
16 import org.asamk.signal.manager.ProvisioningManager;
17 import org.asamk.signal.manager.RegistrationManager;
18 import org.asamk.signal.manager.ServiceConfig;
19 import org.asamk.signal.util.IOUtils;
20 import org.freedesktop.dbus.connections.impl.DBusConnection;
21 import org.freedesktop.dbus.exceptions.DBusException;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
25 import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.stream.Collectors;
32
33 public class Cli {
34
35 private final static Logger logger = LoggerFactory.getLogger(Main.class);
36
37 private final Namespace ns;
38
39 public Cli(final Namespace ns) {
40 this.ns = ns;
41 }
42
43 public int init() {
44 Command command = getCommand();
45 if (command == null) {
46 logger.error("Command not implemented!");
47 return 1;
48 }
49
50 String username = ns.getString("username");
51
52 if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
53 // If username is null, it will connect to the default object path
54 return initDbusClient(command, username, ns.getBoolean("dbus_system"));
55 }
56
57 final File dataPath;
58 String config = ns.getString("config");
59 if (config != null) {
60 dataPath = new File(config);
61 } else {
62 dataPath = getDefaultDataPath();
63 }
64
65 final SignalServiceConfiguration serviceConfiguration = ServiceConfig.createDefaultServiceConfiguration(
66 BaseConfig.USER_AGENT);
67
68 if (!ServiceConfig.getCapabilities().isGv2()) {
69 logger.warn("WARNING: Support for new group V2 is disabled,"
70 + " because the required native library dependency is missing: libzkgroup");
71 }
72
73 if (command instanceof ProvisioningCommand) {
74 if (username != null) {
75 System.err.println("You cannot specify a username (phone number) when linking");
76 return 1;
77 }
78
79 return handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceConfiguration);
80 }
81
82 if (username == null) {
83 List<String> usernames = Manager.getAllLocalUsernames(dataPath);
84 if (usernames.size() == 0) {
85 System.err.println("No local users found, you first need to register or link an account");
86 return 1;
87 }
88
89 if (command instanceof MultiLocalCommand) {
90 return handleMultiLocalCommand((MultiLocalCommand) command, dataPath, serviceConfiguration, usernames);
91 }
92
93 if (usernames.size() > 1) {
94 System.err.println("Multiple users found, you need to specify a username (phone number) with -u");
95 return 1;
96 }
97
98 username = usernames.get(0);
99 } else if (!PhoneNumberFormatter.isValidNumber(username, null)) {
100 System.err.println("Invalid username (phone number), make sure you include the country code.");
101 return 1;
102 }
103
104 if (command instanceof RegistrationCommand) {
105 return handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceConfiguration);
106 }
107
108 if (!(command instanceof LocalCommand)) {
109 System.err.println("Command only works via dbus");
110 return 1;
111 }
112
113 return handleLocalCommand((LocalCommand) command, username, dataPath, serviceConfiguration);
114 }
115
116 private int handleProvisioningCommand(
117 final ProvisioningCommand command,
118 final File dataPath,
119 final SignalServiceConfiguration serviceConfiguration
120 ) {
121 ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
122 return command.handleCommand(ns, pm);
123 }
124
125 private int handleRegistrationCommand(
126 final RegistrationCommand command,
127 final String username,
128 final File dataPath,
129 final SignalServiceConfiguration serviceConfiguration
130 ) {
131 final RegistrationManager manager;
132 try {
133 manager = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
134 } catch (Throwable e) {
135 logger.error("Error loading or creating state file: {}", e.getMessage());
136 return 2;
137 }
138 try (RegistrationManager m = manager) {
139 return command.handleCommand(ns, m);
140 } catch (IOException e) {
141 logger.error("Cleanup failed", e);
142 return 2;
143 }
144 }
145
146 private int handleLocalCommand(
147 final LocalCommand command,
148 final String username,
149 final File dataPath,
150 final SignalServiceConfiguration serviceConfiguration
151 ) {
152 try (Manager m = loadManager(username, dataPath, serviceConfiguration)) {
153 if (m == null) {
154 return 2;
155 }
156
157 return command.handleCommand(ns, m);
158 } catch (IOException e) {
159 logger.error("Cleanup failed", e);
160 return 2;
161 }
162 }
163
164 private int handleMultiLocalCommand(
165 final MultiLocalCommand command,
166 final File dataPath,
167 final SignalServiceConfiguration serviceConfiguration,
168 final List<String> usernames
169 ) {
170 final List<Manager> managers = usernames.stream()
171 .map(u -> loadManager(u, dataPath, serviceConfiguration))
172 .filter(Objects::nonNull)
173 .collect(Collectors.toList());
174
175 int result = command.handleCommand(ns, managers);
176
177 for (Manager m : managers) {
178 try {
179 m.close();
180 } catch (IOException e) {
181 logger.warn("Cleanup failed", e);
182 }
183 }
184 return result;
185 }
186
187 private Manager loadManager(
188 final String username, final File dataPath, final SignalServiceConfiguration serviceConfiguration
189 ) {
190 Manager manager;
191 try {
192 manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
193 } catch (NotRegisteredException e) {
194 logger.error("User " + username + " is not registered.");
195 return null;
196 } catch (Throwable e) {
197 logger.error("Error loading state file for user " + username + ": {}", e.getMessage());
198 return null;
199 }
200
201 try {
202 manager.checkAccountState();
203 } catch (IOException e) {
204 logger.error("Error while checking account " + username + ": {}", e.getMessage());
205 return null;
206 }
207
208 return manager;
209 }
210
211 private Command getCommand() {
212 String commandKey = ns.getString("command");
213 return Commands.getCommand(commandKey);
214 }
215
216 private int initDbusClient(final Command command, final String username, final boolean systemBus) {
217 try {
218 DBusConnection.DBusBusType busType;
219 if (systemBus) {
220 busType = DBusConnection.DBusBusType.SYSTEM;
221 } else {
222 busType = DBusConnection.DBusBusType.SESSION;
223 }
224 try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) {
225 Signal ts = dBusConn.getRemoteObject(DbusConfig.getBusname(),
226 DbusConfig.getObjectPath(username),
227 Signal.class);
228
229 return handleCommand(command, ts, dBusConn);
230 }
231 } catch (DBusException | IOException e) {
232 logger.error("Dbus client failed", e);
233 return 2;
234 }
235 }
236
237 private int handleCommand(Command command, Signal ts, DBusConnection dBusConn) {
238 if (command instanceof ExtendedDbusCommand) {
239 return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
240 } else if (command instanceof DbusCommand) {
241 return ((DbusCommand) command).handleCommand(ns, ts);
242 } else {
243 System.err.println("Command is not yet implemented via dbus");
244 return 1;
245 }
246 }
247
248 /**
249 * Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
250 * - $HOME/.config/signal
251 * - $HOME/.config/textsecure
252 *
253 * @return the data directory to be used by signal-cli.
254 */
255 private static File getDefaultDataPath() {
256 File dataPath = new File(IOUtils.getDataHomeDir(), "signal-cli");
257 if (dataPath.exists()) {
258 return dataPath;
259 }
260
261 File configPath = new File(System.getProperty("user.home"), ".config");
262
263 File legacySettingsPath = new File(configPath, "signal");
264 if (legacySettingsPath.exists()) {
265 return legacySettingsPath;
266 }
267
268 legacySettingsPath = new File(configPath, "textsecure");
269 if (legacySettingsPath.exists()) {
270 return legacySettingsPath;
271 }
272
273 return dataPath;
274 }
275 }