]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/Main.java
Change default data path to $XDG_DATA_HOME/signal-cli
[signal-cli] / src / main / java / org / asamk / signal / Main.java
1 /*
2 Copyright (C) 2015-2018 AsamK
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.*;
22 import org.apache.http.util.TextUtils;
23 import org.asamk.Signal;
24 import org.asamk.signal.commands.*;
25 import org.asamk.signal.manager.BaseConfig;
26 import org.asamk.signal.manager.Manager;
27 import org.asamk.signal.util.IOUtils;
28 import org.asamk.signal.util.SecurityProvider;
29 import org.bouncycastle.jce.provider.BouncyCastleProvider;
30 import org.freedesktop.dbus.DBusConnection;
31 import org.freedesktop.dbus.exceptions.DBusException;
32 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
33
34 import java.io.File;
35 import java.security.Security;
36 import java.util.Map;
37
38 public class Main {
39
40 public static void main(String[] args) {
41 // Register our own security provider
42 Security.insertProviderAt(new SecurityProvider(), 1);
43 Security.addProvider(new BouncyCastleProvider());
44
45 Namespace ns = parseArgs(args);
46 if (ns == null) {
47 System.exit(1);
48 }
49
50 int res = handleCommands(ns);
51 System.exit(res);
52 }
53
54 private static int handleCommands(Namespace ns) {
55 final String username = ns.getString("username");
56 Manager m;
57 Signal ts;
58 DBusConnection dBusConn = null;
59 try {
60 if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
61 try {
62 m = null;
63 int busType;
64 if (ns.getBoolean("dbus_system")) {
65 busType = DBusConnection.SYSTEM;
66 } else {
67 busType = DBusConnection.SESSION;
68 }
69 dBusConn = DBusConnection.getConnection(busType);
70 ts = dBusConn.getRemoteObject(
71 DbusConfig.SIGNAL_BUSNAME, DbusConfig.SIGNAL_OBJECTPATH,
72 Signal.class);
73 } catch (UnsatisfiedLinkError e) {
74 System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
75 return 1;
76 } catch (DBusException e) {
77 e.printStackTrace();
78 if (dBusConn != null) {
79 dBusConn.disconnect();
80 }
81 return 3;
82 }
83 } else {
84 String dataPath = ns.getString("config");
85 if (TextUtils.isEmpty(dataPath)) {
86 dataPath = getDefaultDataPath();
87 }
88
89 m = new Manager(username, dataPath);
90 ts = m;
91 try {
92 m.init();
93 } catch (Exception e) {
94 System.err.println("Error loading state file: " + e.getMessage());
95 return 2;
96 }
97 }
98
99 String commandKey = ns.getString("command");
100 final Map<String, Command> commands = Commands.getCommands();
101 if (commands.containsKey(commandKey)) {
102 Command command = commands.get(commandKey);
103
104 if (dBusConn != null) {
105 if (command instanceof ExtendedDbusCommand) {
106 return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
107 } else if (command instanceof DbusCommand) {
108 return ((DbusCommand) command).handleCommand(ns, ts);
109 } else {
110 System.err.println(commandKey + " is not yet implemented via dbus");
111 return 1;
112 }
113 } else {
114 if (command instanceof LocalCommand) {
115 return ((LocalCommand) command).handleCommand(ns, m);
116 } else if (command instanceof DbusCommand) {
117 return ((DbusCommand) command).handleCommand(ns, ts);
118 } else {
119 System.err.println(commandKey + " is only works via dbus");
120 return 1;
121 }
122 }
123 }
124 return 0;
125 } finally {
126 if (dBusConn != null) {
127 dBusConn.disconnect();
128 }
129 }
130 }
131
132 /**
133 * Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
134 * - $HOME/.config/signal
135 * - $HOME/.config/textsecure
136 *
137 * @return the data directory to be used by signal-cli.
138 */
139 private static String getDefaultDataPath() {
140 String dataPath = IOUtils.getDataHomeDir() + "/signal-cli";
141 if (new File(dataPath).exists()) {
142 return dataPath;
143 }
144
145 String legacySettingsPath = System.getProperty("user.home") + "/.config/signal";
146 if (new File(legacySettingsPath).exists()) {
147 return legacySettingsPath;
148 }
149
150 legacySettingsPath = System.getProperty("user.home") + "/.config/textsecure";
151 if (new File(legacySettingsPath).exists()) {
152 return legacySettingsPath;
153 }
154
155 return dataPath;
156 }
157
158 private static Namespace parseArgs(String[] args) {
159 ArgumentParser parser = ArgumentParsers.newFor("signal-cli")
160 .build()
161 .defaultHelp(true)
162 .description("Commandline interface for Signal.")
163 .version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION);
164
165 parser.addArgument("-v", "--version")
166 .help("Show package version.")
167 .action(Arguments.version());
168 parser.addArgument("--config")
169 .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
170
171 MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
172 mut.addArgument("-u", "--username")
173 .help("Specify your phone number, that will be used for verification.");
174 mut.addArgument("--dbus")
175 .help("Make request via user dbus.")
176 .action(Arguments.storeTrue());
177 mut.addArgument("--dbus-system")
178 .help("Make request via system dbus.")
179 .action(Arguments.storeTrue());
180
181 Subparsers subparsers = parser.addSubparsers()
182 .title("subcommands")
183 .dest("command")
184 .description("valid subcommands")
185 .help("additional help");
186
187 final Map<String, Command> commands = Commands.getCommands();
188 for (Map.Entry<String, Command> entry : commands.entrySet()) {
189 Subparser subparser = subparsers.addParser(entry.getKey());
190 entry.getValue().attachToSubparser(subparser);
191 }
192
193 Namespace ns;
194 try {
195 ns = parser.parseArgs(args);
196 } catch (ArgumentParserException e) {
197 parser.handleError(e);
198 return null;
199 }
200
201 if ("link".equals(ns.getString("command"))) {
202 if (ns.getString("username") != null) {
203 parser.printUsage();
204 System.err.println("You cannot specify a username (phone number) when linking");
205 System.exit(2);
206 }
207 } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
208 if (ns.getString("username") == null) {
209 parser.printUsage();
210 System.err.println("You need to specify a username (phone number)");
211 System.exit(2);
212 }
213 if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"))) {
214 System.err.println("Invalid username (phone number), make sure you include the country code.");
215 System.exit(2);
216 }
217 }
218 if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
219 System.err.println("You cannot specify recipients by phone number and groups a the same time");
220 System.exit(2);
221 }
222 return ns;
223 }
224 }