]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/Main.java
4b22605c21a279045cad86d4f9016859bda5d1f6
[signal-cli] / src / main / java / org / asamk / signal / Main.java
1 /**
2 * Copyright (C) 2015 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.freedesktop.dbus.DBusConnection;
25 import org.freedesktop.dbus.DBusSigHandler;
26 import org.freedesktop.dbus.exceptions.DBusException;
27 import org.freedesktop.dbus.exceptions.DBusExecutionException;
28 import org.whispersystems.libsignal.InvalidKeyException;
29 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
30 import org.whispersystems.signalservice.api.messages.*;
31 import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
32 import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
33 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
34 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
35 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
36 import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
37 import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
38 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
39 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
40
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.StringWriter;
45 import java.net.URI;
46 import java.net.URISyntaxException;
47 import java.nio.charset.Charset;
48 import java.security.Security;
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.concurrent.TimeoutException;
52
53 public class Main {
54
55 public static final String SIGNAL_BUSNAME = "org.asamk.Signal";
56 public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
57
58 public static void main(String[] args) {
59 // Workaround for BKS truststore
60 Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1);
61
62 Namespace ns = parseArgs(args);
63 if (ns == null) {
64 System.exit(1);
65 }
66
67 final String username = ns.getString("username");
68 Manager m;
69 Signal ts;
70 DBusConnection dBusConn = null;
71 try {
72 if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
73 try {
74 m = null;
75 int busType;
76 if (ns.getBoolean("dbus_system")) {
77 busType = DBusConnection.SYSTEM;
78 } else {
79 busType = DBusConnection.SESSION;
80 }
81 dBusConn = DBusConnection.getConnection(busType);
82 ts = (Signal) dBusConn.getRemoteObject(
83 SIGNAL_BUSNAME, SIGNAL_OBJECTPATH,
84 Signal.class);
85 } catch (DBusException e) {
86 e.printStackTrace();
87 if (dBusConn != null) {
88 dBusConn.disconnect();
89 }
90 System.exit(3);
91 return;
92 }
93 } else {
94 String settingsPath = ns.getString("config");
95 if (TextUtils.isEmpty(settingsPath)) {
96 settingsPath = System.getProperty("user.home") + "/.config/signal";
97 if (!new File(settingsPath).exists()) {
98 String legacySettingsPath = System.getProperty("user.home") + "/.config/textsecure";
99 if (new File(legacySettingsPath).exists()) {
100 settingsPath = legacySettingsPath;
101 }
102 }
103 }
104
105 m = new Manager(username, settingsPath);
106 ts = m;
107 if (m.userExists()) {
108 try {
109 m.load();
110 } catch (Exception e) {
111 System.err.println("Error loading state file \"" + m.getFileName() + "\": " + e.getMessage());
112 System.exit(2);
113 return;
114 }
115 }
116 }
117
118 switch (ns.getString("command")) {
119 case "register":
120 if (dBusConn != null) {
121 System.err.println("register is not yet implemented via dbus");
122 System.exit(1);
123 }
124 if (!m.userHasKeys()) {
125 m.createNewIdentity();
126 }
127 try {
128 m.register(ns.getBoolean("voice"));
129 } catch (IOException e) {
130 System.err.println("Request verify error: " + e.getMessage());
131 System.exit(3);
132 }
133 break;
134 case "verify":
135 if (dBusConn != null) {
136 System.err.println("verify is not yet implemented via dbus");
137 System.exit(1);
138 }
139 if (!m.userHasKeys()) {
140 System.err.println("User has no keys, first call register.");
141 System.exit(1);
142 }
143 if (m.isRegistered()) {
144 System.err.println("User registration is already verified");
145 System.exit(1);
146 }
147 try {
148 m.verifyAccount(ns.getString("verificationCode"));
149 } catch (IOException e) {
150 System.err.println("Verify error: " + e.getMessage());
151 System.exit(3);
152 }
153 break;
154 case "link":
155 if (dBusConn != null) {
156 System.err.println("link is not yet implemented via dbus");
157 System.exit(1);
158 }
159
160 // When linking, username is null and we always have to create keys
161 m.createNewIdentity();
162
163 String deviceName = ns.getString("name");
164 if (deviceName == null) {
165 deviceName = "cli";
166 }
167 try {
168 System.out.println(m.getDeviceLinkUri());
169 m.finishDeviceLink(deviceName);
170 System.out.println("Associated with: " + m.getUsername());
171 } catch (TimeoutException e) {
172 System.err.println("Link request timed out, please try again.");
173 System.exit(3);
174 } catch (IOException e) {
175 System.err.println("Link request error: " + e.getMessage());
176 System.exit(3);
177 } catch (InvalidKeyException e) {
178 e.printStackTrace();
179 System.exit(3);
180 } catch (UserAlreadyExists e) {
181 System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again.");
182 System.exit(3);
183 }
184 break;
185 case "addDevice":
186 if (dBusConn != null) {
187 System.err.println("link is not yet implemented via dbus");
188 System.exit(1);
189 }
190 if (!m.isRegistered()) {
191 System.err.println("User is not registered.");
192 System.exit(1);
193 }
194 try {
195 m.addDeviceLink(new URI(ns.getString("uri")));
196 } catch (IOException e) {
197 e.printStackTrace();
198 System.exit(3);
199 } catch (InvalidKeyException e) {
200 e.printStackTrace();
201 System.exit(2);
202 } catch (URISyntaxException e) {
203 e.printStackTrace();
204 System.exit(2);
205 }
206 break;
207 case "listDevices":
208 if (dBusConn != null) {
209 System.err.println("listDevices is not yet implemented via dbus");
210 System.exit(1);
211 }
212 if (!m.isRegistered()) {
213 System.err.println("User is not registered.");
214 System.exit(1);
215 }
216 try {
217 List<DeviceInfo> devices = m.getLinkedDevices();
218 for (DeviceInfo d : devices) {
219 System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":");
220 System.out.println(" Name: " + d.getName());
221 System.out.println(" Created: " + d.getCreated());
222 System.out.println(" Last seen: " + d.getLastSeen());
223 }
224 } catch (IOException e) {
225 e.printStackTrace();
226 System.exit(3);
227 }
228 break;
229 case "removeDevice":
230 if (dBusConn != null) {
231 System.err.println("removeDevice is not yet implemented via dbus");
232 System.exit(1);
233 }
234 if (!m.isRegistered()) {
235 System.err.println("User is not registered.");
236 System.exit(1);
237 }
238 try {
239 int deviceId = ns.getInt("deviceId");
240 m.removeLinkedDevices(deviceId);
241 } catch (IOException e) {
242 e.printStackTrace();
243 System.exit(3);
244 }
245 break;
246 case "send":
247 if (dBusConn == null && !m.isRegistered()) {
248 System.err.println("User is not registered.");
249 System.exit(1);
250 }
251
252 if (ns.getBoolean("endsession")) {
253 if (ns.getList("recipient") == null) {
254 System.err.println("No recipients given");
255 System.err.println("Aborting sending.");
256 System.exit(1);
257 }
258 try {
259 ts.sendEndSessionMessage(ns.<String>getList("recipient"));
260 } catch (IOException e) {
261 handleIOException(e);
262 } catch (EncapsulatedExceptions e) {
263 handleEncapsulatedExceptions(e);
264 } catch (AssertionError e) {
265 handleAssertionError(e);
266 } catch (DBusExecutionException e) {
267 handleDBusExecutionException(e);
268 } catch (UntrustedIdentityException e) {
269 e.printStackTrace();
270 }
271 } else {
272 String messageText = ns.getString("message");
273 if (messageText == null) {
274 try {
275 messageText = readAll(System.in);
276 } catch (IOException e) {
277 System.err.println("Failed to read message from stdin: " + e.getMessage());
278 System.err.println("Aborting sending.");
279 System.exit(1);
280 }
281 }
282
283 try {
284 List<String> attachments = ns.getList("attachment");
285 if (attachments == null) {
286 attachments = new ArrayList<>();
287 }
288 if (ns.getString("group") != null) {
289 byte[] groupId = decodeGroupId(ns.getString("group"));
290 ts.sendGroupMessage(messageText, attachments, groupId);
291 } else {
292 ts.sendMessage(messageText, attachments, ns.<String>getList("recipient"));
293 }
294 } catch (IOException e) {
295 handleIOException(e);
296 } catch (EncapsulatedExceptions e) {
297 handleEncapsulatedExceptions(e);
298 } catch (AssertionError e) {
299 handleAssertionError(e);
300 } catch (GroupNotFoundException e) {
301 handleGroupNotFoundException(e);
302 } catch (AttachmentInvalidException e) {
303 System.err.println("Failed to add attachment: " + e.getMessage());
304 System.err.println("Aborting sending.");
305 System.exit(1);
306 } catch (DBusExecutionException e) {
307 handleDBusExecutionException(e);
308 } catch (UntrustedIdentityException e) {
309 e.printStackTrace();
310 }
311 }
312
313 break;
314 case "receive":
315 if (dBusConn != null) {
316 try {
317 dBusConn.addSigHandler(Signal.MessageReceived.class, new DBusSigHandler<Signal.MessageReceived>() {
318 @Override
319 public void handle(Signal.MessageReceived s) {
320 System.out.print(String.format("Envelope from: %s\nTimestamp: %d\nBody: %s\n",
321 s.getSender(), s.getTimestamp(), s.getMessage()));
322 if (s.getGroupId().length > 0) {
323 System.out.println("Group info:");
324 System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId()));
325 }
326 if (s.getAttachments().size() > 0) {
327 System.out.println("Attachments: ");
328 for (String attachment : s.getAttachments()) {
329 System.out.println("- Stored plaintext in: " + attachment);
330 }
331 }
332 System.out.println();
333 }
334 });
335 } catch (DBusException e) {
336 e.printStackTrace();
337 }
338 while (true) {
339 try {
340 Thread.sleep(10000);
341 } catch (InterruptedException e) {
342 System.exit(0);
343 }
344 }
345 }
346 if (!m.isRegistered()) {
347 System.err.println("User is not registered.");
348 System.exit(1);
349 }
350 int timeout = 5;
351 if (ns.getInt("timeout") != null) {
352 timeout = ns.getInt("timeout");
353 }
354 boolean returnOnTimeout = true;
355 if (timeout < 0) {
356 returnOnTimeout = false;
357 timeout = 3600;
358 }
359 try {
360 m.receiveMessages(timeout, returnOnTimeout, new ReceiveMessageHandler(m));
361 } catch (IOException e) {
362 System.err.println("Error while receiving messages: " + e.getMessage());
363 System.exit(3);
364 } catch (AssertionError e) {
365 handleAssertionError(e);
366 }
367 break;
368 case "quitGroup":
369 if (dBusConn != null) {
370 System.err.println("quitGroup is not yet implemented via dbus");
371 System.exit(1);
372 }
373 if (!m.isRegistered()) {
374 System.err.println("User is not registered.");
375 System.exit(1);
376 }
377
378 try {
379 m.sendQuitGroupMessage(decodeGroupId(ns.getString("group")));
380 } catch (IOException e) {
381 handleIOException(e);
382 } catch (EncapsulatedExceptions e) {
383 handleEncapsulatedExceptions(e);
384 } catch (AssertionError e) {
385 handleAssertionError(e);
386 } catch (GroupNotFoundException e) {
387 handleGroupNotFoundException(e);
388 } catch (UntrustedIdentityException e) {
389 e.printStackTrace();
390 }
391
392 break;
393 case "updateGroup":
394 if (dBusConn != null) {
395 System.err.println("updateGroup is not yet implemented via dbus");
396 System.exit(1);
397 }
398 if (!m.isRegistered()) {
399 System.err.println("User is not registered.");
400 System.exit(1);
401 }
402
403 try {
404 byte[] groupId = null;
405 if (ns.getString("group") != null) {
406 groupId = decodeGroupId(ns.getString("group"));
407 }
408 byte[] newGroupId = m.sendUpdateGroupMessage(groupId, ns.getString("name"), ns.<String>getList("member"), ns.getString("avatar"));
409 if (groupId == null) {
410 System.out.println("Creating new group \"" + Base64.encodeBytes(newGroupId) + "\" …");
411 }
412 } catch (IOException e) {
413 handleIOException(e);
414 } catch (AttachmentInvalidException e) {
415 System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
416 System.err.println("Aborting sending.");
417 System.exit(1);
418 } catch (GroupNotFoundException e) {
419 handleGroupNotFoundException(e);
420 } catch (EncapsulatedExceptions e) {
421 handleEncapsulatedExceptions(e);
422 } catch (UntrustedIdentityException e) {
423 e.printStackTrace();
424 }
425
426 break;
427 case "daemon":
428 if (dBusConn != null) {
429 System.err.println("Stop it.");
430 System.exit(1);
431 }
432 if (!m.isRegistered()) {
433 System.err.println("User is not registered.");
434 System.exit(1);
435 }
436 DBusConnection conn = null;
437 try {
438 try {
439 int busType;
440 if (ns.getBoolean("system")) {
441 busType = DBusConnection.SYSTEM;
442 } else {
443 busType = DBusConnection.SESSION;
444 }
445 conn = DBusConnection.getConnection(busType);
446 conn.exportObject(SIGNAL_OBJECTPATH, m);
447 conn.requestBusName(SIGNAL_BUSNAME);
448 } catch (DBusException e) {
449 e.printStackTrace();
450 System.exit(3);
451 }
452 try {
453 m.receiveMessages(3600, false, new DbusReceiveMessageHandler(m, conn));
454 } catch (IOException e) {
455 System.err.println("Error while receiving messages: " + e.getMessage());
456 System.exit(3);
457 } catch (AssertionError e) {
458 handleAssertionError(e);
459 }
460 } finally {
461 if (conn != null) {
462 conn.disconnect();
463 }
464 }
465
466 break;
467 }
468 System.exit(0);
469 } finally {
470 if (dBusConn != null) {
471 dBusConn.disconnect();
472 }
473 }
474 }
475
476 private static void handleGroupNotFoundException(GroupNotFoundException e) {
477 System.err.println("Failed to send to group: " + e.getMessage());
478 System.err.println("Aborting sending.");
479 System.exit(1);
480 }
481
482 private static void handleDBusExecutionException(DBusExecutionException e) {
483 System.err.println("Cannot connect to dbus: " + e.getMessage());
484 System.err.println("Aborting.");
485 System.exit(1);
486 }
487
488 private static byte[] decodeGroupId(String groupId) {
489 try {
490 return Base64.decode(groupId);
491 } catch (IOException e) {
492 System.err.println("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage());
493 System.err.println("Aborting sending.");
494 System.exit(1);
495 return null;
496 }
497 }
498
499 private static Namespace parseArgs(String[] args) {
500 ArgumentParser parser = ArgumentParsers.newArgumentParser("signal-cli")
501 .defaultHelp(true)
502 .description("Commandline interface for Signal.")
503 .version(Manager.PROJECT_NAME + " " + Manager.PROJECT_VERSION);
504
505 parser.addArgument("-v", "--version")
506 .help("Show package version.")
507 .action(Arguments.version());
508 parser.addArgument("--config")
509 .help("Set the path, where to store the config (Default: $HOME/.config/signal).");
510
511 MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
512 mut.addArgument("-u", "--username")
513 .help("Specify your phone number, that will be used for verification.");
514 mut.addArgument("--dbus")
515 .help("Make request via user dbus.")
516 .action(Arguments.storeTrue());
517 mut.addArgument("--dbus-system")
518 .help("Make request via system dbus.")
519 .action(Arguments.storeTrue());
520
521 Subparsers subparsers = parser.addSubparsers()
522 .title("subcommands")
523 .dest("command")
524 .description("valid subcommands")
525 .help("additional help");
526
527 Subparser parserLink = subparsers.addParser("link");
528 parserLink.addArgument("-n", "--name")
529 .help("Specify a name to describe this new device.");
530
531 Subparser parserAddDevice = subparsers.addParser("addDevice");
532 parserAddDevice.addArgument("--uri")
533 .required(true)
534 .help("Specify the uri contained in the QR code shown by the new device.");
535
536 Subparser parserDevices = subparsers.addParser("listDevices");
537
538 Subparser parserRemoveDevice = subparsers.addParser("removeDevice");
539 parserRemoveDevice.addArgument("-d", "--deviceId")
540 .type(int.class)
541 .required(true)
542 .help("Specify the device you want to remove. Use listDevices to see the deviceIds.");
543
544 Subparser parserRegister = subparsers.addParser("register");
545 parserRegister.addArgument("-v", "--voice")
546 .help("The verification should be done over voice, not sms.")
547 .action(Arguments.storeTrue());
548
549 Subparser parserVerify = subparsers.addParser("verify");
550 parserVerify.addArgument("verificationCode")
551 .help("The verification code you received via sms or voice call.");
552
553 Subparser parserSend = subparsers.addParser("send");
554 parserSend.addArgument("-g", "--group")
555 .help("Specify the recipient group ID.");
556 parserSend.addArgument("recipient")
557 .help("Specify the recipients' phone number.")
558 .nargs("*");
559 parserSend.addArgument("-m", "--message")
560 .help("Specify the message, if missing standard input is used.");
561 parserSend.addArgument("-a", "--attachment")
562 .nargs("*")
563 .help("Add file as attachment");
564 parserSend.addArgument("-e", "--endsession")
565 .help("Clear session state and send end session message.")
566 .action(Arguments.storeTrue());
567
568 Subparser parserLeaveGroup = subparsers.addParser("quitGroup");
569 parserLeaveGroup.addArgument("-g", "--group")
570 .required(true)
571 .help("Specify the recipient group ID.");
572
573 Subparser parserUpdateGroup = subparsers.addParser("updateGroup");
574 parserUpdateGroup.addArgument("-g", "--group")
575 .help("Specify the recipient group ID.");
576 parserUpdateGroup.addArgument("-n", "--name")
577 .help("Specify the new group name.");
578 parserUpdateGroup.addArgument("-a", "--avatar")
579 .help("Specify a new group avatar image file");
580 parserUpdateGroup.addArgument("-m", "--member")
581 .nargs("*")
582 .help("Specify one or more members to add to the group");
583
584 Subparser parserReceive = subparsers.addParser("receive");
585 parserReceive.addArgument("-t", "--timeout")
586 .type(int.class)
587 .help("Number of seconds to wait for new messages (negative values disable timeout)");
588
589 Subparser parserDaemon = subparsers.addParser("daemon");
590 parserDaemon.addArgument("--system")
591 .action(Arguments.storeTrue())
592 .help("Use DBus system bus instead of user bus.");
593
594 try {
595 Namespace ns = parser.parseArgs(args);
596 if ("link".equals(ns.getString("command"))) {
597 if (ns.getString("username") != null) {
598 parser.printUsage();
599 System.err.println("You cannot specify a username (phone number) when linking");
600 System.exit(2);
601 }
602 } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
603 if (ns.getString("username") == null) {
604 parser.printUsage();
605 System.err.println("You need to specify a username (phone number)");
606 System.exit(2);
607 }
608 if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"))) {
609 System.err.println("Invalid username (phone number), make sure you include the country code.");
610 System.exit(2);
611 }
612 }
613 if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
614 System.err.println("You cannot specify recipients by phone number and groups a the same time");
615 System.exit(2);
616 }
617 return ns;
618 } catch (ArgumentParserException e) {
619 parser.handleError(e);
620 return null;
621 }
622 }
623
624 private static void handleAssertionError(AssertionError e) {
625 System.err.println("Failed to send/receive message (Assertion): " + e.getMessage());
626 e.printStackTrace();
627 System.err.println("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
628 System.exit(1);
629 }
630
631 private static void handleEncapsulatedExceptions(EncapsulatedExceptions e) {
632 System.err.println("Failed to send (some) messages:");
633 for (NetworkFailureException n : e.getNetworkExceptions()) {
634 System.err.println("Network failure for \"" + n.getE164number() + "\": " + n.getMessage());
635 }
636 for (UnregisteredUserException n : e.getUnregisteredUserExceptions()) {
637 System.err.println("Unregistered user \"" + n.getE164Number() + "\": " + n.getMessage());
638 }
639 for (UntrustedIdentityException n : e.getUntrustedIdentityExceptions()) {
640 System.err.println("Untrusted Identity for \"" + n.getE164Number() + "\": " + n.getMessage());
641 }
642 }
643
644 private static void handleIOException(IOException e) {
645 System.err.println("Failed to send message: " + e.getMessage());
646 }
647
648 private static String readAll(InputStream in) throws IOException {
649 StringWriter output = new StringWriter();
650 byte[] buffer = new byte[4096];
651 long count = 0;
652 int n;
653 while (-1 != (n = System.in.read(buffer))) {
654 output.write(new String(buffer, 0, n, Charset.defaultCharset()));
655 count += n;
656 }
657 return output.toString();
658 }
659
660 private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
661 final Manager m;
662
663 public ReceiveMessageHandler(Manager m) {
664 this.m = m;
665 }
666
667 @Override
668 public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, GroupInfo group) {
669 SignalServiceAddress source = envelope.getSourceAddress();
670 System.out.println(String.format("Envelope from: %s (device: %d)", source.getNumber(), envelope.getSourceDevice()));
671 if (source.getRelay().isPresent()) {
672 System.out.println("Relayed by: " + source.getRelay().get());
673 }
674 System.out.println("Timestamp: " + envelope.getTimestamp());
675
676 if (envelope.isReceipt()) {
677 System.out.println("Got receipt.");
678 } else if (envelope.isSignalMessage() | envelope.isPreKeySignalMessage()) {
679 if (content == null) {
680 System.out.println("Failed to decrypt message.");
681 } else {
682 if (content.getDataMessage().isPresent()) {
683 SignalServiceDataMessage message = content.getDataMessage().get();
684 handleSignalServiceDataMessage(message, group);
685 }
686 if (content.getSyncMessage().isPresent()) {
687 System.out.println("Received a sync message");
688 SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
689
690 if (syncMessage.getContacts().isPresent()) {
691 System.out.println("Received sync contacts");
692 printAttachment(syncMessage.getContacts().get());
693 }
694 if (syncMessage.getGroups().isPresent()) {
695 System.out.println("Received sync groups");
696 printAttachment(syncMessage.getGroups().get());
697 }
698 if (syncMessage.getRead().isPresent()) {
699 System.out.println("Received sync read messages list");
700 for (ReadMessage rm : syncMessage.getRead().get()) {
701 System.out.println("From: " + rm.getSender() + " Message timestamp: " + rm.getTimestamp());
702 }
703 }
704 if (syncMessage.getRequest().isPresent()) {
705 System.out.println("Received sync request");
706 if (syncMessage.getRequest().get().isContactsRequest()) {
707 System.out.println(" - contacts request");
708 }
709 if (syncMessage.getRequest().get().isGroupsRequest()) {
710 System.out.println(" - groups request");
711 }
712 }
713 if (syncMessage.getSent().isPresent()) {
714 System.out.println("Received sync sent message");
715 final SentTranscriptMessage sentTranscriptMessage = syncMessage.getSent().get();
716 System.out.println("To: " + (sentTranscriptMessage.getDestination().isPresent() ? sentTranscriptMessage.getDestination().get() : "Unknown") + " , Message timestamp: " + sentTranscriptMessage.getTimestamp());
717 SignalServiceDataMessage message = sentTranscriptMessage.getMessage();
718 handleSignalServiceDataMessage(message, null);
719 }
720 }
721 }
722 } else {
723 System.out.println("Unknown message received.");
724 }
725 System.out.println();
726 }
727
728 // TODO remove group parameter
729 private void handleSignalServiceDataMessage(SignalServiceDataMessage message, GroupInfo group) {
730 System.out.println("Message timestamp: " + message.getTimestamp());
731
732 if (message.getBody().isPresent()) {
733 System.out.println("Body: " + message.getBody().get());
734 }
735 if (message.getGroupInfo().isPresent()) {
736 SignalServiceGroup groupInfo = message.getGroupInfo().get();
737 System.out.println("Group info:");
738 System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId()));
739 if (groupInfo.getName().isPresent()) {
740 System.out.println(" Name: " + groupInfo.getName().get());
741 } else if (group != null) {
742 System.out.println(" Name: " + group.name);
743 } else {
744 System.out.println(" Name: <Unknown group>");
745 }
746 System.out.println(" Type: " + groupInfo.getType());
747 if (groupInfo.getMembers().isPresent()) {
748 for (String member : groupInfo.getMembers().get()) {
749 System.out.println(" Member: " + member);
750 }
751 }
752 if (groupInfo.getAvatar().isPresent()) {
753 System.out.println(" Avatar:");
754 printAttachment(groupInfo.getAvatar().get());
755 }
756 }
757 if (message.isEndSession()) {
758 System.out.println("Is end session");
759 }
760
761 if (message.getAttachments().isPresent()) {
762 System.out.println("Attachments: ");
763 for (SignalServiceAttachment attachment : message.getAttachments().get()) {
764 printAttachment(attachment);
765 }
766 }
767 }
768
769 private void printAttachment(SignalServiceAttachment attachment) {
770 System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")");
771 if (attachment.isPointer()) {
772 final SignalServiceAttachmentPointer pointer = attachment.asPointer();
773 System.out.println(" Id: " + pointer.getId() + " Key length: " + pointer.getKey().length + (pointer.getRelay().isPresent() ? " Relay: " + pointer.getRelay().get() : ""));
774 System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "<unavailable>") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : ""));
775 File file = m.getAttachmentFile(pointer.getId());
776 if (file.exists()) {
777 System.out.println(" Stored plaintext in: " + file);
778 }
779 }
780 }
781 }
782
783 private static class DbusReceiveMessageHandler extends ReceiveMessageHandler {
784 final DBusConnection conn;
785
786 public DbusReceiveMessageHandler(Manager m, DBusConnection conn) {
787 super(m);
788 this.conn = conn;
789 }
790
791 @Override
792 public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, GroupInfo group) {
793 super.handleMessage(envelope, content, group);
794
795 if (!envelope.isReceipt() && content != null && content.getDataMessage().isPresent()) {
796 SignalServiceDataMessage message = content.getDataMessage().get();
797
798 if (!message.isEndSession() &&
799 !(message.getGroupInfo().isPresent() &&
800 message.getGroupInfo().get().getType() != SignalServiceGroup.Type.DELIVER)) {
801 List<String> attachments = new ArrayList<>();
802 if (message.getAttachments().isPresent()) {
803 for (SignalServiceAttachment attachment : message.getAttachments().get()) {
804 if (attachment.isPointer()) {
805 attachments.add(m.getAttachmentFile(attachment.asPointer().getId()).getAbsolutePath());
806 }
807 }
808 }
809
810 try {
811 conn.sendSignal(new Signal.MessageReceived(
812 SIGNAL_OBJECTPATH,
813 message.getTimestamp(),
814 envelope.getSource(),
815 message.getGroupInfo().isPresent() ? message.getGroupInfo().get().getGroupId() : new byte[0],
816 message.getBody().isPresent() ? message.getBody().get() : "",
817 attachments));
818 } catch (DBusException e) {
819 e.printStackTrace();
820 }
821 }
822 }
823 }
824
825 private void printAttachment(SignalServiceAttachment attachment) {
826 System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")");
827 if (attachment.isPointer()) {
828 final SignalServiceAttachmentPointer pointer = attachment.asPointer();
829 System.out.println(" Id: " + pointer.getId() + " Key length: " + pointer.getKey().length + (pointer.getRelay().isPresent() ? " Relay: " + pointer.getRelay().get() : ""));
830 System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "<unavailable>") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : ""));
831 File file = m.getAttachmentFile(pointer.getId());
832 if (file.exists()) {
833 System.out.println(" Stored plaintext in: " + file);
834 }
835 }
836 }
837 }
838 }