]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/Main.java
Refactor Manager and SignalAccount to implement Closeable
[signal-cli] / src / main / java / org / asamk / signal / Main.java
1 /*
2 Copyright (C) 2015-2020 AsamK and contributors
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 package org.asamk.signal;
18
19 import net.sourceforge.argparse4j.ArgumentParsers;
20 import net.sourceforge.argparse4j.impl.Arguments;
21 import net.sourceforge.argparse4j.inf.ArgumentParser;
22 import net.sourceforge.argparse4j.inf.ArgumentParserException;
23 import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
24 import net.sourceforge.argparse4j.inf.Namespace;
25 import net.sourceforge.argparse4j.inf.Subparser;
26 import net.sourceforge.argparse4j.inf.Subparsers;
27
28 import org.asamk.Signal;
29 import org.asamk.signal.commands.Command;
30 import org.asamk.signal.commands.Commands;
31 import org.asamk.signal.commands.DbusCommand;
32 import org.asamk.signal.commands.ExtendedDbusCommand;
33 import org.asamk.signal.commands.LocalCommand;
34 import org.asamk.signal.commands.ProvisioningCommand;
35 import org.asamk.signal.manager.Manager;
36 import org.asamk.signal.manager.ProvisioningManager;
37 import org.asamk.signal.manager.ServiceConfig;
38 import org.asamk.signal.util.IOUtils;
39 import org.asamk.signal.util.SecurityProvider;
40 import org.bouncycastle.jce.provider.BouncyCastleProvider;
41 import org.freedesktop.dbus.connections.impl.DBusConnection;
42 import org.freedesktop.dbus.exceptions.DBusException;
43 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
44 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.security.Security;
49 import java.util.Map;
50
51 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
52
53 public class Main {
54
55 public static void main(String[] args) {
56 installSecurityProviderWorkaround();
57
58 Namespace ns = parseArgs(args);
59 if (ns == null) {
60 System.exit(1);
61 }
62
63 int res = handleCommands(ns);
64 System.exit(res);
65 }
66
67 public static void installSecurityProviderWorkaround() {
68 // Register our own security provider
69 Security.insertProviderAt(new SecurityProvider(), 1);
70 Security.addProvider(new BouncyCastleProvider());
71 }
72
73 private static int handleCommands(Namespace ns) {
74 final String username = ns.getString("username");
75
76 if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
77 try {
78 DBusConnection.DBusBusType busType;
79 if (ns.getBoolean("dbus_system")) {
80 busType = DBusConnection.DBusBusType.SYSTEM;
81 } else {
82 busType = DBusConnection.DBusBusType.SESSION;
83 }
84 try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) {
85 Signal ts = dBusConn.getRemoteObject(
86 DbusConfig.SIGNAL_BUSNAME, DbusConfig.SIGNAL_OBJECTPATH,
87 Signal.class);
88
89 return handleCommands(ns, ts, dBusConn);
90 }
91 } catch (UnsatisfiedLinkError e) {
92 System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
93 return 1;
94 } catch (DBusException | IOException e) {
95 e.printStackTrace();
96 return 3;
97 }
98 } else {
99 String dataPath = ns.getString("config");
100 if (isEmpty(dataPath)) {
101 dataPath = getDefaultDataPath();
102 }
103
104 if (username == null) {
105 ProvisioningManager pm = new ProvisioningManager(dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
106 return handleCommands(ns, pm);
107 }
108
109 Manager manager;
110 try {
111 manager = Manager.init(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
112 } catch (Throwable e) {
113 System.err.println("Error loading state file: " + e.getMessage());
114 return 2;
115 }
116
117 try (Manager m = manager) {
118 try {
119 m.checkAccountState();
120 } catch (AuthorizationFailedException e) {
121 if (!"register".equals(ns.getString("command"))) {
122 // Register command should still be possible, if current authorization fails
123 System.err.println("Authorization failed, was the number registered elsewhere?");
124 return 2;
125 }
126 } catch (IOException e) {
127 System.err.println("Error while checking account: " + e.getMessage());
128 return 2;
129 }
130
131 return handleCommands(ns, m);
132 } catch (IOException e) {
133 e.printStackTrace();
134 return 3;
135 }
136 }
137 }
138
139 private static int handleCommands(Namespace ns, Signal ts, DBusConnection dBusConn) {
140 String commandKey = ns.getString("command");
141 final Map<String, Command> commands = Commands.getCommands();
142 if (commands.containsKey(commandKey)) {
143 Command command = commands.get(commandKey);
144
145 if (command instanceof ExtendedDbusCommand) {
146 return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
147 } else if (command instanceof DbusCommand) {
148 return ((DbusCommand) command).handleCommand(ns, ts);
149 } else {
150 System.err.println(commandKey + " is not yet implemented via dbus");
151 return 1;
152 }
153 }
154 return 0;
155 }
156
157 private static int handleCommands(Namespace ns, ProvisioningManager pm) {
158 String commandKey = ns.getString("command");
159 final Map<String, Command> commands = Commands.getCommands();
160 if (commands.containsKey(commandKey)) {
161 Command command = commands.get(commandKey);
162
163 if (command instanceof ProvisioningCommand) {
164 return ((ProvisioningCommand) command).handleCommand(ns, pm);
165 } else {
166 System.err.println(commandKey + " only works with a username");
167 return 1;
168 }
169 }
170 return 0;
171 }
172
173 private static int handleCommands(Namespace ns, Manager m) {
174 String commandKey = ns.getString("command");
175 final Map<String, Command> commands = Commands.getCommands();
176 if (commands.containsKey(commandKey)) {
177 Command command = commands.get(commandKey);
178
179 if (command instanceof LocalCommand) {
180 return ((LocalCommand) command).handleCommand(ns, m);
181 } else if (command instanceof DbusCommand) {
182 return ((DbusCommand) command).handleCommand(ns, m);
183 } else if (command instanceof ExtendedDbusCommand) {
184 System.err.println(commandKey + " only works via dbus");
185 }
186 return 1;
187 }
188 return 0;
189 }
190
191 /**
192 * Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
193 * - $HOME/.config/signal
194 * - $HOME/.config/textsecure
195 *
196 * @return the data directory to be used by signal-cli.
197 */
198 private static String getDefaultDataPath() {
199 String dataPath = IOUtils.getDataHomeDir() + "/signal-cli";
200 if (new File(dataPath).exists()) {
201 return dataPath;
202 }
203
204 String legacySettingsPath = System.getProperty("user.home") + "/.config/signal";
205 if (new File(legacySettingsPath).exists()) {
206 return legacySettingsPath;
207 }
208
209 legacySettingsPath = System.getProperty("user.home") + "/.config/textsecure";
210 if (new File(legacySettingsPath).exists()) {
211 return legacySettingsPath;
212 }
213
214 return dataPath;
215 }
216
217 private static Namespace parseArgs(String[] args) {
218 ArgumentParser parser = ArgumentParsers.newFor("signal-cli")
219 .build()
220 .defaultHelp(true)
221 .description("Commandline interface for Signal.")
222 .version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION);
223
224 parser.addArgument("-v", "--version")
225 .help("Show package version.")
226 .action(Arguments.version());
227 parser.addArgument("--config")
228 .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
229
230 MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
231 mut.addArgument("-u", "--username")
232 .help("Specify your phone number, that will be used for verification.");
233 mut.addArgument("--dbus")
234 .help("Make request via user dbus.")
235 .action(Arguments.storeTrue());
236 mut.addArgument("--dbus-system")
237 .help("Make request via system dbus.")
238 .action(Arguments.storeTrue());
239
240 Subparsers subparsers = parser.addSubparsers()
241 .title("subcommands")
242 .dest("command")
243 .description("valid subcommands")
244 .help("additional help");
245
246 final Map<String, Command> commands = Commands.getCommands();
247 for (Map.Entry<String, Command> entry : commands.entrySet()) {
248 Subparser subparser = subparsers.addParser(entry.getKey());
249 entry.getValue().attachToSubparser(subparser);
250 }
251
252 Namespace ns;
253 try {
254 ns = parser.parseArgs(args);
255 } catch (ArgumentParserException e) {
256 parser.handleError(e);
257 return null;
258 }
259
260 if ("link".equals(ns.getString("command"))) {
261 if (ns.getString("username") != null) {
262 parser.printUsage();
263 System.err.println("You cannot specify a username (phone number) when linking");
264 System.exit(2);
265 }
266 } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
267 if (ns.getString("username") == null) {
268 parser.printUsage();
269 System.err.println("You need to specify a username (phone number)");
270 System.exit(2);
271 }
272 if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"), null)) {
273 System.err.println("Invalid username (phone number), make sure you include the country code.");
274 System.exit(2);
275 }
276 }
277 if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
278 System.err.println("You cannot specify recipients by phone number and groups at the same time");
279 System.exit(2);
280 }
281 return ns;
282 }
283 }