]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/Main.java
Prevent NullPointerException when sending sync groups
[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 }
269 } else {
270 String messageText = ns.getString("message");
271 if (messageText == null) {
272 try {
273 messageText = readAll(System.in);
274 } catch (IOException e) {
275 System.err.println("Failed to read message from stdin: " + e.getMessage());
276 System.err.println("Aborting sending.");
277 System.exit(1);
278 }
279 }
280
281 try {
282 List<String> attachments = ns.getList("attachment");
283 if (attachments == null) {
284 attachments = new ArrayList<>();
285 }
286 if (ns.getString("group") != null) {
287 byte[] groupId = decodeGroupId(ns.getString("group"));
288 ts.sendGroupMessage(messageText, attachments, groupId);
289 } else {
290 ts.sendMessage(messageText, attachments, ns.<String>getList("recipient"));
291 }
292 } catch (IOException e) {
293 handleIOException(e);
294 } catch (EncapsulatedExceptions e) {
295 handleEncapsulatedExceptions(e);
296 } catch (AssertionError e) {
297 handleAssertionError(e);
298 } catch (GroupNotFoundException e) {
299 handleGroupNotFoundException(e);
300 } catch (AttachmentInvalidException e) {
301 System.err.println("Failed to add attachment: " + e.getMessage());
302 System.err.println("Aborting sending.");
303 System.exit(1);
304 } catch (DBusExecutionException e) {
305 handleDBusExecutionException(e);
306 }
307 }
308
309 break;
310 case "receive":
311 if (dBusConn != null) {
312 try {
313 dBusConn.addSigHandler(Signal.MessageReceived.class, new DBusSigHandler<Signal.MessageReceived>() {
314 @Override
315 public void handle(Signal.MessageReceived s) {
316 System.out.print(String.format("Envelope from: %s\nTimestamp: %d\nBody: %s\n",
317 s.getSender(), s.getTimestamp(), s.getMessage()));
318 if (s.getGroupId().length > 0) {
319 System.out.println("Group info:");
320 System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId()));
321 }
322 if (s.getAttachments().size() > 0) {
323 System.out.println("Attachments: ");
324 for (String attachment : s.getAttachments()) {
325 System.out.println("- Stored plaintext in: " + attachment);
326 }
327 }
328 System.out.println();
329 }
330 });
331 } catch (DBusException e) {
332 e.printStackTrace();
333 }
334 while (true) {
335 try {
336 Thread.sleep(10000);
337 } catch (InterruptedException e) {
338 System.exit(0);
339 }
340 }
341 }
342 if (!m.isRegistered()) {
343 System.err.println("User is not registered.");
344 System.exit(1);
345 }
346 int timeout = 5;
347 if (ns.getInt("timeout") != null) {
348 timeout = ns.getInt("timeout");
349 }
350 boolean returnOnTimeout = true;
351 if (timeout < 0) {
352 returnOnTimeout = false;
353 timeout = 3600;
354 }
355 try {
356 m.receiveMessages(timeout, returnOnTimeout, new ReceiveMessageHandler(m));
357 } catch (IOException e) {
358 System.err.println("Error while receiving messages: " + e.getMessage());
359 System.exit(3);
360 } catch (AssertionError e) {
361 handleAssertionError(e);
362 }
363 break;
364 case "quitGroup":
365 if (dBusConn != null) {
366 System.err.println("quitGroup is not yet implemented via dbus");
367 System.exit(1);
368 }
369 if (!m.isRegistered()) {
370 System.err.println("User is not registered.");
371 System.exit(1);
372 }
373
374 try {
375 m.sendQuitGroupMessage(decodeGroupId(ns.getString("group")));
376 } catch (IOException e) {
377 handleIOException(e);
378 } catch (EncapsulatedExceptions e) {
379 handleEncapsulatedExceptions(e);
380 } catch (AssertionError e) {
381 handleAssertionError(e);
382 } catch (GroupNotFoundException e) {
383 handleGroupNotFoundException(e);
384 }
385
386 break;
387 case "updateGroup":
388 if (dBusConn != null) {
389 System.err.println("updateGroup is not yet implemented via dbus");
390 System.exit(1);
391 }
392 if (!m.isRegistered()) {
393 System.err.println("User is not registered.");
394 System.exit(1);
395 }
396
397 try {
398 byte[] groupId = null;
399 if (ns.getString("group") != null) {
400 groupId = decodeGroupId(ns.getString("group"));
401 }
402 byte[] newGroupId = m.sendUpdateGroupMessage(groupId, ns.getString("name"), ns.<String>getList("member"), ns.getString("avatar"));
403 if (groupId == null) {
404 System.out.println("Creating new group \"" + Base64.encodeBytes(newGroupId) + "\" …");
405 }
406 } catch (IOException e) {
407 handleIOException(e);
408 } catch (AttachmentInvalidException e) {
409 System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
410 System.err.println("Aborting sending.");
411 System.exit(1);
412 } catch (GroupNotFoundException e) {
413 handleGroupNotFoundException(e);
414 } catch (EncapsulatedExceptions e) {
415 handleEncapsulatedExceptions(e);
416 }
417
418 break;
419 case "daemon":
420 if (dBusConn != null) {
421 System.err.println("Stop it.");
422 System.exit(1);
423 }
424 if (!m.isRegistered()) {
425 System.err.println("User is not registered.");
426 System.exit(1);
427 }
428 DBusConnection conn = null;
429 try {
430 try {
431 int busType;
432 if (ns.getBoolean("system")) {
433 busType = DBusConnection.SYSTEM;
434 } else {
435 busType = DBusConnection.SESSION;
436 }
437 conn = DBusConnection.getConnection(busType);
438 conn.exportObject(SIGNAL_OBJECTPATH, m);
439 conn.requestBusName(SIGNAL_BUSNAME);
440 } catch (DBusException e) {
441 e.printStackTrace();
442 System.exit(3);
443 }
444 try {
445 m.receiveMessages(3600, false, new DbusReceiveMessageHandler(m, conn));
446 } catch (IOException e) {
447 System.err.println("Error while receiving messages: " + e.getMessage());
448 System.exit(3);
449 } catch (AssertionError e) {
450 handleAssertionError(e);
451 }
452 } finally {
453 if (conn != null) {
454 conn.disconnect();
455 }
456 }
457
458 break;
459 }
460 System.exit(0);
461 } finally {
462 if (dBusConn != null) {
463 dBusConn.disconnect();
464 }
465 }
466 }
467
468 private static void handleGroupNotFoundException(GroupNotFoundException e) {
469 System.err.println("Failed to send to group: " + e.getMessage());
470 System.err.println("Aborting sending.");
471 System.exit(1);
472 }
473
474 private static void handleDBusExecutionException(DBusExecutionException e) {
475 System.err.println("Cannot connect to dbus: " + e.getMessage());
476 System.err.println("Aborting.");
477 System.exit(1);
478 }
479
480 private static byte[] decodeGroupId(String groupId) {
481 try {
482 return Base64.decode(groupId);
483 } catch (IOException e) {
484 System.err.println("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage());
485 System.err.println("Aborting sending.");
486 System.exit(1);
487 return null;
488 }
489 }
490
491 private static Namespace parseArgs(String[] args) {
492 ArgumentParser parser = ArgumentParsers.newArgumentParser("signal-cli")
493 .defaultHelp(true)
494 .description("Commandline interface for Signal.")
495 .version(Manager.PROJECT_NAME + " " + Manager.PROJECT_VERSION);
496
497 parser.addArgument("-v", "--version")
498 .help("Show package version.")
499 .action(Arguments.version());
500 parser.addArgument("--config")
501 .help("Set the path, where to store the config (Default: $HOME/.config/signal).");
502
503 MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
504 mut.addArgument("-u", "--username")
505 .help("Specify your phone number, that will be used for verification.");
506 mut.addArgument("--dbus")
507 .help("Make request via user dbus.")
508 .action(Arguments.storeTrue());
509 mut.addArgument("--dbus-system")
510 .help("Make request via system dbus.")
511 .action(Arguments.storeTrue());
512
513 Subparsers subparsers = parser.addSubparsers()
514 .title("subcommands")
515 .dest("command")
516 .description("valid subcommands")
517 .help("additional help");
518
519 Subparser parserLink = subparsers.addParser("link");
520 parserLink.addArgument("-n", "--name")
521 .help("Specify a name to describe this new device.");
522
523 Subparser parserAddDevice = subparsers.addParser("addDevice");
524 parserAddDevice.addArgument("--uri")
525 .required(true)
526 .help("Specify the uri contained in the QR code shown by the new device.");
527
528 Subparser parserDevices = subparsers.addParser("listDevices");
529
530 Subparser parserRemoveDevice = subparsers.addParser("removeDevice");
531 parserRemoveDevice.addArgument("-d", "--deviceId")
532 .type(int.class)
533 .required(true)
534 .help("Specify the device you want to remove. Use listDevices to see the deviceIds.");
535
536 Subparser parserRegister = subparsers.addParser("register");
537 parserRegister.addArgument("-v", "--voice")
538 .help("The verification should be done over voice, not sms.")
539 .action(Arguments.storeTrue());
540
541 Subparser parserVerify = subparsers.addParser("verify");
542 parserVerify.addArgument("verificationCode")
543 .help("The verification code you received via sms or voice call.");
544
545 Subparser parserSend = subparsers.addParser("send");
546 parserSend.addArgument("-g", "--group")
547 .help("Specify the recipient group ID.");
548 parserSend.addArgument("recipient")
549 .help("Specify the recipients' phone number.")
550 .nargs("*");
551 parserSend.addArgument("-m", "--message")
552 .help("Specify the message, if missing standard input is used.");
553 parserSend.addArgument("-a", "--attachment")
554 .nargs("*")
555 .help("Add file as attachment");
556 parserSend.addArgument("-e", "--endsession")
557 .help("Clear session state and send end session message.")
558 .action(Arguments.storeTrue());
559
560 Subparser parserLeaveGroup = subparsers.addParser("quitGroup");
561 parserLeaveGroup.addArgument("-g", "--group")
562 .required(true)
563 .help("Specify the recipient group ID.");
564
565 Subparser parserUpdateGroup = subparsers.addParser("updateGroup");
566 parserUpdateGroup.addArgument("-g", "--group")
567 .help("Specify the recipient group ID.");
568 parserUpdateGroup.addArgument("-n", "--name")
569 .help("Specify the new group name.");
570 parserUpdateGroup.addArgument("-a", "--avatar")
571 .help("Specify a new group avatar image file");
572 parserUpdateGroup.addArgument("-m", "--member")
573 .nargs("*")
574 .help("Specify one or more members to add to the group");
575
576 Subparser parserReceive = subparsers.addParser("receive");
577 parserReceive.addArgument("-t", "--timeout")
578 .type(int.class)
579 .help("Number of seconds to wait for new messages (negative values disable timeout)");
580
581 Subparser parserDaemon = subparsers.addParser("daemon");
582 parserDaemon.addArgument("--system")
583 .action(Arguments.storeTrue())
584 .help("Use DBus system bus instead of user bus.");
585
586 try {
587 Namespace ns = parser.parseArgs(args);
588 if ("link".equals(ns.getString("command"))) {
589 if (ns.getString("username") != null) {
590 parser.printUsage();
591 System.err.println("You cannot specify a username (phone number) when linking");
592 System.exit(2);
593 }
594 } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
595 if (ns.getString("username") == null) {
596 parser.printUsage();
597 System.err.println("You need to specify a username (phone number)");
598 System.exit(2);
599 }
600 if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"))) {
601 System.err.println("Invalid username (phone number), make sure you include the country code.");
602 System.exit(2);
603 }
604 }
605 if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
606 System.err.println("You cannot specify recipients by phone number and groups a the same time");
607 System.exit(2);
608 }
609 return ns;
610 } catch (ArgumentParserException e) {
611 parser.handleError(e);
612 return null;
613 }
614 }
615
616 private static void handleAssertionError(AssertionError e) {
617 System.err.println("Failed to send/receive message (Assertion): " + e.getMessage());
618 e.printStackTrace();
619 System.err.println("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
620 System.exit(1);
621 }
622
623 private static void handleEncapsulatedExceptions(EncapsulatedExceptions e) {
624 System.err.println("Failed to send (some) messages:");
625 for (NetworkFailureException n : e.getNetworkExceptions()) {
626 System.err.println("Network failure for \"" + n.getE164number() + "\": " + n.getMessage());
627 }
628 for (UnregisteredUserException n : e.getUnregisteredUserExceptions()) {
629 System.err.println("Unregistered user \"" + n.getE164Number() + "\": " + n.getMessage());
630 }
631 for (UntrustedIdentityException n : e.getUntrustedIdentityExceptions()) {
632 System.err.println("Untrusted Identity for \"" + n.getE164Number() + "\": " + n.getMessage());
633 }
634 }
635
636 private static void handleIOException(IOException e) {
637 System.err.println("Failed to send message: " + e.getMessage());
638 }
639
640 private static String readAll(InputStream in) throws IOException {
641 StringWriter output = new StringWriter();
642 byte[] buffer = new byte[4096];
643 long count = 0;
644 int n;
645 while (-1 != (n = System.in.read(buffer))) {
646 output.write(new String(buffer, 0, n, Charset.defaultCharset()));
647 count += n;
648 }
649 return output.toString();
650 }
651
652 private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
653 final Manager m;
654
655 public ReceiveMessageHandler(Manager m) {
656 this.m = m;
657 }
658
659 @Override
660 public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) {
661 SignalServiceAddress source = envelope.getSourceAddress();
662 ContactInfo sourceContact = m.getContact(source.getNumber());
663 System.out.println(String.format("Envelope from: %s (device: %d)", (sourceContact == null ? "" : "“" + sourceContact.name + "” ") + source.getNumber(), envelope.getSourceDevice()));
664 if (source.getRelay().isPresent()) {
665 System.out.println("Relayed by: " + source.getRelay().get());
666 }
667 System.out.println("Timestamp: " + envelope.getTimestamp());
668
669 if (envelope.isReceipt()) {
670 System.out.println("Got receipt.");
671 } else if (envelope.isSignalMessage() | envelope.isPreKeySignalMessage()) {
672 if (content == null) {
673 System.out.println("Failed to decrypt message.");
674 } else {
675 if (content.getDataMessage().isPresent()) {
676 SignalServiceDataMessage message = content.getDataMessage().get();
677 handleSignalServiceDataMessage(message);
678 }
679 if (content.getSyncMessage().isPresent()) {
680 System.out.println("Received a sync message");
681 SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
682
683 if (syncMessage.getContacts().isPresent()) {
684 System.out.println("Received sync contacts");
685 printAttachment(syncMessage.getContacts().get());
686 }
687 if (syncMessage.getGroups().isPresent()) {
688 System.out.println("Received sync groups");
689 printAttachment(syncMessage.getGroups().get());
690 }
691 if (syncMessage.getRead().isPresent()) {
692 System.out.println("Received sync read messages list");
693 for (ReadMessage rm : syncMessage.getRead().get()) {
694 ContactInfo fromContact = m.getContact(rm.getSender());
695 System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender() + " Message timestamp: " + rm.getTimestamp());
696 }
697 }
698 if (syncMessage.getRequest().isPresent()) {
699 System.out.println("Received sync request");
700 if (syncMessage.getRequest().get().isContactsRequest()) {
701 System.out.println(" - contacts request");
702 }
703 if (syncMessage.getRequest().get().isGroupsRequest()) {
704 System.out.println(" - groups request");
705 }
706 }
707 if (syncMessage.getSent().isPresent()) {
708 System.out.println("Received sync sent message");
709 final SentTranscriptMessage sentTranscriptMessage = syncMessage.getSent().get();
710 String to;
711 if (sentTranscriptMessage.getDestination().isPresent()) {
712 String dest = sentTranscriptMessage.getDestination().get();
713 ContactInfo destContact = m.getContact(dest);
714 to = (destContact == null ? "" : "“" + destContact.name + "” ") + dest;
715 } else {
716 to = "Unknown";
717 }
718 System.out.println("To: " + to + " , Message timestamp: " + sentTranscriptMessage.getTimestamp());
719 SignalServiceDataMessage message = sentTranscriptMessage.getMessage();
720 handleSignalServiceDataMessage(message);
721 }
722 }
723 }
724 } else {
725 System.out.println("Unknown message received.");
726 }
727 System.out.println();
728 }
729
730 private void handleSignalServiceDataMessage(SignalServiceDataMessage message) {
731 System.out.println("Message timestamp: " + message.getTimestamp());
732
733 if (message.getBody().isPresent()) {
734 System.out.println("Body: " + message.getBody().get());
735 }
736 if (message.getGroupInfo().isPresent()) {
737 SignalServiceGroup groupInfo = message.getGroupInfo().get();
738 System.out.println("Group info:");
739 System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId()));
740 if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) {
741 System.out.println(" Name: " + groupInfo.getName().get());
742 } else {
743 GroupInfo group = m.getGroup(groupInfo.getGroupId());
744 if (group != null) {
745 System.out.println(" Name: " + group.name);
746 } else {
747 System.out.println(" Name: <Unknown group>");
748 }
749 }
750 System.out.println(" Type: " + groupInfo.getType());
751 if (groupInfo.getMembers().isPresent()) {
752 for (String member : groupInfo.getMembers().get()) {
753 System.out.println(" Member: " + member);
754 }
755 }
756 if (groupInfo.getAvatar().isPresent()) {
757 System.out.println(" Avatar:");
758 printAttachment(groupInfo.getAvatar().get());
759 }
760 }
761 if (message.isEndSession()) {
762 System.out.println("Is end session");
763 }
764
765 if (message.getAttachments().isPresent()) {
766 System.out.println("Attachments: ");
767 for (SignalServiceAttachment attachment : message.getAttachments().get()) {
768 printAttachment(attachment);
769 }
770 }
771 }
772
773 private void printAttachment(SignalServiceAttachment attachment) {
774 System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")");
775 if (attachment.isPointer()) {
776 final SignalServiceAttachmentPointer pointer = attachment.asPointer();
777 System.out.println(" Id: " + pointer.getId() + " Key length: " + pointer.getKey().length + (pointer.getRelay().isPresent() ? " Relay: " + pointer.getRelay().get() : ""));
778 System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "<unavailable>") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : ""));
779 File file = m.getAttachmentFile(pointer.getId());
780 if (file.exists()) {
781 System.out.println(" Stored plaintext in: " + file);
782 }
783 }
784 }
785 }
786
787 private static class DbusReceiveMessageHandler extends ReceiveMessageHandler {
788 final DBusConnection conn;
789
790 public DbusReceiveMessageHandler(Manager m, DBusConnection conn) {
791 super(m);
792 this.conn = conn;
793 }
794
795 @Override
796 public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content) {
797 super.handleMessage(envelope, content);
798
799 if (!envelope.isReceipt() && content != null && content.getDataMessage().isPresent()) {
800 SignalServiceDataMessage message = content.getDataMessage().get();
801
802 if (!message.isEndSession() &&
803 !(message.getGroupInfo().isPresent() &&
804 message.getGroupInfo().get().getType() != SignalServiceGroup.Type.DELIVER)) {
805 List<String> attachments = new ArrayList<>();
806 if (message.getAttachments().isPresent()) {
807 for (SignalServiceAttachment attachment : message.getAttachments().get()) {
808 if (attachment.isPointer()) {
809 attachments.add(m.getAttachmentFile(attachment.asPointer().getId()).getAbsolutePath());
810 }
811 }
812 }
813
814 try {
815 conn.sendSignal(new Signal.MessageReceived(
816 SIGNAL_OBJECTPATH,
817 message.getTimestamp(),
818 envelope.getSource(),
819 message.getGroupInfo().isPresent() ? message.getGroupInfo().get().getGroupId() : new byte[0],
820 message.getBody().isPresent() ? message.getBody().get() : "",
821 attachments));
822 } catch (DBusException e) {
823 e.printStackTrace();
824 }
825 }
826 }
827 }
828
829 }
830 }