]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
Do not send message resend request to own device
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / IncomingMessageHandler.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.JobExecutor;
4 import org.asamk.signal.manager.Manager;
5 import org.asamk.signal.manager.SignalDependencies;
6 import org.asamk.signal.manager.TrustLevel;
7 import org.asamk.signal.manager.UntrustedIdentityException;
8 import org.asamk.signal.manager.actions.HandleAction;
9 import org.asamk.signal.manager.actions.RefreshPreKeysAction;
10 import org.asamk.signal.manager.actions.RenewSessionAction;
11 import org.asamk.signal.manager.actions.RetrieveProfileAction;
12 import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
13 import org.asamk.signal.manager.actions.SendGroupInfoAction;
14 import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
15 import org.asamk.signal.manager.actions.SendReceiptAction;
16 import org.asamk.signal.manager.actions.SendRetryMessageRequestAction;
17 import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
18 import org.asamk.signal.manager.actions.SendSyncContactsAction;
19 import org.asamk.signal.manager.actions.SendSyncGroupsAction;
20 import org.asamk.signal.manager.actions.SendSyncKeysAction;
21 import org.asamk.signal.manager.groups.GroupId;
22 import org.asamk.signal.manager.groups.GroupNotFoundException;
23 import org.asamk.signal.manager.groups.GroupUtils;
24 import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
25 import org.asamk.signal.manager.storage.SignalAccount;
26 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
27 import org.asamk.signal.manager.storage.recipients.Profile;
28 import org.asamk.signal.manager.storage.recipients.RecipientId;
29 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
30 import org.asamk.signal.manager.storage.stickers.Sticker;
31 import org.asamk.signal.manager.storage.stickers.StickerPackId;
32 import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
33 import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
34 import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
35 import org.signal.libsignal.metadata.ProtocolNoSessionException;
36 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
37 import org.signal.libsignal.metadata.SelfSendException;
38 import org.signal.zkgroup.InvalidInputException;
39 import org.signal.zkgroup.profiles.ProfileKey;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.whispersystems.libsignal.SignalProtocolAddress;
43 import org.whispersystems.libsignal.util.Pair;
44 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
45 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
46 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
47 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
48 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
49 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
50 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
51
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.stream.Collectors;
55
56 public final class IncomingMessageHandler {
57
58 private final static Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
59
60 private final SignalAccount account;
61 private final SignalDependencies dependencies;
62 private final RecipientResolver recipientResolver;
63 private final SignalServiceAddressResolver addressResolver;
64 private final GroupHelper groupHelper;
65 private final ContactHelper contactHelper;
66 private final AttachmentHelper attachmentHelper;
67 private final SyncHelper syncHelper;
68 private final ProfileProvider profileProvider;
69 private final JobExecutor jobExecutor;
70
71 public IncomingMessageHandler(
72 final SignalAccount account,
73 final SignalDependencies dependencies,
74 final RecipientResolver recipientResolver,
75 final SignalServiceAddressResolver addressResolver,
76 final GroupHelper groupHelper,
77 final ContactHelper contactHelper,
78 final AttachmentHelper attachmentHelper,
79 final SyncHelper syncHelper,
80 final ProfileProvider profileProvider,
81 final JobExecutor jobExecutor
82 ) {
83 this.account = account;
84 this.dependencies = dependencies;
85 this.recipientResolver = recipientResolver;
86 this.addressResolver = addressResolver;
87 this.groupHelper = groupHelper;
88 this.contactHelper = contactHelper;
89 this.attachmentHelper = attachmentHelper;
90 this.syncHelper = syncHelper;
91 this.profileProvider = profileProvider;
92 this.jobExecutor = jobExecutor;
93 }
94
95 public Pair<List<HandleAction>, Exception> handleRetryEnvelope(
96 final SignalServiceEnvelope envelope,
97 final boolean ignoreAttachments,
98 final Manager.ReceiveMessageHandler handler
99 ) {
100 final List<HandleAction> actions = new ArrayList<>();
101 if (envelope.isPreKeySignalMessage()) {
102 actions.add(RefreshPreKeysAction.create());
103 }
104
105 SignalServiceContent content = null;
106 if (!envelope.isReceipt()) {
107 try {
108 content = dependencies.getCipher().decrypt(envelope);
109 } catch (ProtocolUntrustedIdentityException e) {
110 final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
111 final var exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(
112 recipientId), e.getSenderDevice());
113 return new Pair<>(List.of(), exception);
114 } catch (Exception e) {
115 return new Pair<>(List.of(), e);
116 }
117 }
118 actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, null));
119 return new Pair<>(actions, null);
120 }
121
122 public Pair<List<HandleAction>, Exception> handleEnvelope(
123 final SignalServiceEnvelope envelope,
124 final boolean ignoreAttachments,
125 final Manager.ReceiveMessageHandler handler
126 ) {
127 final var actions = new ArrayList<HandleAction>();
128 if (envelope.hasSourceUuid()) {
129 // Store uuid if we don't have it already
130 // address/uuid in envelope is sent by server
131 account.getRecipientStore().resolveRecipientTrusted(envelope.getSourceAddress());
132 }
133 SignalServiceContent content = null;
134 Exception exception = null;
135 if (!envelope.isReceipt()) {
136 try {
137 content = dependencies.getCipher().decrypt(envelope);
138 } catch (ProtocolUntrustedIdentityException e) {
139 final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
140 actions.add(new RetrieveProfileAction(recipientId));
141 exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
142 e.getSenderDevice());
143 } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) {
144 final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
145 final var senderProfile = profileProvider.getProfile(sender);
146 final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
147 if (e.getSenderDevice() != account.getDeviceId()
148 && senderProfile != null
149 && senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
150 && selfProfile != null
151 && selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
152 logger.debug("Received invalid message, requesting message resend.");
153 actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
154 } else {
155 logger.debug("Received invalid message, queuing renew session action.");
156 actions.add(new RenewSessionAction(sender));
157 }
158 exception = e;
159 } catch (SelfSendException e) {
160 logger.debug("Dropping unidentified message from self.");
161 return new Pair<>(List.of(), null);
162 } catch (Exception e) {
163 exception = e;
164 }
165 }
166
167 actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, exception));
168 return new Pair<>(actions, exception);
169 }
170
171 private List<HandleAction> checkAndHandleMessage(
172 final SignalServiceEnvelope envelope,
173 final SignalServiceContent content,
174 final boolean ignoreAttachments,
175 final Manager.ReceiveMessageHandler handler,
176 final Exception exception
177 ) {
178 if (!envelope.hasSourceUuid() && content != null) {
179 // Store uuid if we don't have it already
180 // address/uuid is validated by unidentified sender certificate
181 account.getRecipientStore().resolveRecipientTrusted(content.getSender());
182 }
183 if (isMessageBlocked(envelope, content)) {
184 logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
185 return List.of();
186 } else if (isNotAllowedToSendToGroup(envelope, content)) {
187 logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
188 (envelope.hasSourceUuid() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
189 envelope.getTimestamp());
190 return List.of();
191 } else {
192 List<HandleAction> actions;
193 if (content != null) {
194 actions = handleMessage(envelope, content, ignoreAttachments);
195 } else {
196 actions = List.of();
197 }
198 handler.handleMessage(envelope, content, exception);
199 return actions;
200 }
201 }
202
203 public List<HandleAction> handleMessage(
204 SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments
205 ) {
206 var actions = new ArrayList<HandleAction>();
207 final RecipientId sender;
208 final int senderDeviceId;
209 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
210 sender = recipientResolver.resolveRecipient(envelope.getSourceAddress());
211 senderDeviceId = envelope.getSourceDevice();
212 } else {
213 sender = recipientResolver.resolveRecipient(content.getSender());
214 senderDeviceId = content.getSenderDevice();
215 }
216
217 if (content.getSenderKeyDistributionMessage().isPresent()) {
218 final var message = content.getSenderKeyDistributionMessage().get();
219 final var protocolAddress = new SignalProtocolAddress(addressResolver.resolveSignalServiceAddress(sender)
220 .getIdentifier(), senderDeviceId);
221 dependencies.getMessageSender().processSenderKeyDistributionMessage(protocolAddress, message);
222 }
223
224 if (content.getDataMessage().isPresent()) {
225 var message = content.getDataMessage().get();
226
227 if (content.isNeedsReceipt()) {
228 actions.add(new SendReceiptAction(sender, message.getTimestamp()));
229 }
230
231 actions.addAll(handleSignalServiceDataMessage(message,
232 false,
233 sender,
234 account.getSelfRecipientId(),
235 ignoreAttachments));
236 }
237
238 if (content.getSyncMessage().isPresent()) {
239 var syncMessage = content.getSyncMessage().get();
240 actions.addAll(handleSyncMessage(syncMessage, sender, ignoreAttachments));
241 }
242
243 return actions;
244 }
245
246 private List<HandleAction> handleSyncMessage(
247 final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments
248 ) {
249 var actions = new ArrayList<HandleAction>();
250 account.setMultiDevice(true);
251 if (syncMessage.getSent().isPresent()) {
252 var message = syncMessage.getSent().get();
253 final var destination = message.getDestination().orNull();
254 actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
255 true,
256 sender,
257 destination == null ? null : recipientResolver.resolveRecipient(destination),
258 ignoreAttachments));
259 }
260 if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
261 var rm = syncMessage.getRequest().get();
262 if (rm.isContactsRequest()) {
263 actions.add(SendSyncContactsAction.create());
264 }
265 if (rm.isGroupsRequest()) {
266 actions.add(SendSyncGroupsAction.create());
267 }
268 if (rm.isBlockedListRequest()) {
269 actions.add(SendSyncBlockedListAction.create());
270 }
271 if (rm.isKeysRequest()) {
272 actions.add(SendSyncKeysAction.create());
273 }
274 // TODO Handle rm.isConfigurationRequest();
275 }
276 if (syncMessage.getGroups().isPresent()) {
277 logger.warn("Received a group v1 sync message, that can't be handled anymore, ignoring.");
278 }
279 if (syncMessage.getBlockedList().isPresent()) {
280 final var blockedListMessage = syncMessage.getBlockedList().get();
281 for (var address : blockedListMessage.getAddresses()) {
282 contactHelper.setContactBlocked(recipientResolver.resolveRecipient(address), true);
283 }
284 for (var groupId : blockedListMessage.getGroupIds()
285 .stream()
286 .map(GroupId::unknownVersion)
287 .collect(Collectors.toSet())) {
288 try {
289 groupHelper.setGroupBlocked(groupId, true);
290 } catch (GroupNotFoundException e) {
291 logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
292 groupId.toBase64());
293 }
294 }
295 }
296 if (syncMessage.getContacts().isPresent()) {
297 try {
298 final var contactsMessage = syncMessage.getContacts().get();
299 attachmentHelper.retrieveAttachment(contactsMessage.getContactsStream(),
300 syncHelper::handleSyncDeviceContacts);
301 } catch (Exception e) {
302 logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
303 }
304 }
305 if (syncMessage.getVerified().isPresent()) {
306 final var verifiedMessage = syncMessage.getVerified().get();
307 account.getIdentityKeyStore()
308 .setIdentityTrustLevel(account.getRecipientStore()
309 .resolveRecipientTrusted(verifiedMessage.getDestination()),
310 verifiedMessage.getIdentityKey(),
311 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
312 }
313 if (syncMessage.getStickerPackOperations().isPresent()) {
314 final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
315 for (var m : stickerPackOperationMessages) {
316 if (!m.getPackId().isPresent()) {
317 continue;
318 }
319 final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
320 final var installed = !m.getType().isPresent()
321 || m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
322
323 var sticker = account.getStickerStore().getSticker(stickerPackId);
324 if (m.getPackKey().isPresent()) {
325 if (sticker == null) {
326 sticker = new Sticker(stickerPackId, m.getPackKey().get());
327 }
328 if (installed) {
329 jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
330 }
331 }
332
333 if (sticker != null) {
334 sticker.setInstalled(installed);
335 account.getStickerStore().updateSticker(sticker);
336 }
337 }
338 }
339 if (syncMessage.getFetchType().isPresent()) {
340 switch (syncMessage.getFetchType().get()) {
341 case LOCAL_PROFILE:
342 actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
343 case STORAGE_MANIFEST:
344 actions.add(RetrieveStorageDataAction.create());
345 }
346 }
347 if (syncMessage.getKeys().isPresent()) {
348 final var keysMessage = syncMessage.getKeys().get();
349 if (keysMessage.getStorageService().isPresent()) {
350 final var storageKey = keysMessage.getStorageService().get();
351 account.setStorageKey(storageKey);
352 actions.add(RetrieveStorageDataAction.create());
353 }
354 }
355 if (syncMessage.getConfiguration().isPresent()) {
356 // TODO
357 }
358 return actions;
359 }
360
361 private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
362 SignalServiceAddress source;
363 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
364 source = envelope.getSourceAddress();
365 } else if (content != null) {
366 source = content.getSender();
367 } else {
368 return false;
369 }
370 final var recipientId = recipientResolver.resolveRecipient(source);
371 if (contactHelper.isContactBlocked(recipientId)) {
372 return true;
373 }
374
375 if (content != null && content.getDataMessage().isPresent()) {
376 var message = content.getDataMessage().get();
377 if (message.getGroupContext().isPresent()) {
378 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
379 return groupHelper.isGroupBlocked(groupId);
380 }
381 }
382
383 return false;
384 }
385
386 private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) {
387 SignalServiceAddress source;
388 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
389 source = envelope.getSourceAddress();
390 } else if (content != null) {
391 source = content.getSender();
392 } else {
393 return false;
394 }
395
396 if (content == null || !content.getDataMessage().isPresent()) {
397 return false;
398 }
399
400 var message = content.getDataMessage().get();
401 if (!message.getGroupContext().isPresent()) {
402 return false;
403 }
404
405 if (message.getGroupContext().get().getGroupV1().isPresent()) {
406 var groupInfo = message.getGroupContext().get().getGroupV1().get();
407 if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
408 return false;
409 }
410 }
411
412 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
413 var group = groupHelper.getGroup(groupId);
414 if (group == null) {
415 return false;
416 }
417
418 final var recipientId = recipientResolver.resolveRecipient(source);
419 if (!group.isMember(recipientId)) {
420 return true;
421 }
422
423 if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
424 return message.getBody().isPresent()
425 || message.getAttachments().isPresent()
426 || message.getQuote()
427 .isPresent()
428 || message.getPreviews().isPresent()
429 || message.getMentions().isPresent()
430 || message.getSticker().isPresent();
431 }
432 return false;
433 }
434
435 private List<HandleAction> handleSignalServiceDataMessage(
436 SignalServiceDataMessage message,
437 boolean isSync,
438 RecipientId source,
439 RecipientId destination,
440 boolean ignoreAttachments
441 ) {
442 var actions = new ArrayList<HandleAction>();
443 if (message.getGroupContext().isPresent()) {
444 if (message.getGroupContext().get().getGroupV1().isPresent()) {
445 var groupInfo = message.getGroupContext().get().getGroupV1().get();
446 var groupId = GroupId.v1(groupInfo.getGroupId());
447 var group = groupHelper.getGroup(groupId);
448 if (group == null || group instanceof GroupInfoV1) {
449 var groupV1 = (GroupInfoV1) group;
450 switch (groupInfo.getType()) {
451 case UPDATE: {
452 if (groupV1 == null) {
453 groupV1 = new GroupInfoV1(groupId);
454 }
455
456 if (groupInfo.getAvatar().isPresent()) {
457 var avatar = groupInfo.getAvatar().get();
458 groupHelper.downloadGroupAvatar(groupV1.getGroupId(), avatar);
459 }
460
461 if (groupInfo.getName().isPresent()) {
462 groupV1.name = groupInfo.getName().get();
463 }
464
465 if (groupInfo.getMembers().isPresent()) {
466 groupV1.addMembers(groupInfo.getMembers()
467 .get()
468 .stream()
469 .map(recipientResolver::resolveRecipient)
470 .collect(Collectors.toSet()));
471 }
472
473 account.getGroupStore().updateGroup(groupV1);
474 break;
475 }
476 case DELIVER:
477 if (groupV1 == null && !isSync) {
478 actions.add(new SendGroupInfoRequestAction(source, groupId));
479 }
480 break;
481 case QUIT: {
482 if (groupV1 != null) {
483 groupV1.removeMember(source);
484 account.getGroupStore().updateGroup(groupV1);
485 }
486 break;
487 }
488 case REQUEST_INFO:
489 if (groupV1 != null && !isSync) {
490 actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
491 }
492 break;
493 }
494 } else {
495 // Received a group v1 message for a v2 group
496 }
497 }
498 if (message.getGroupContext().get().getGroupV2().isPresent()) {
499 final var groupContext = message.getGroupContext().get().getGroupV2().get();
500 final var groupMasterKey = groupContext.getMasterKey();
501
502 groupHelper.getOrMigrateGroup(groupMasterKey,
503 groupContext.getRevision(),
504 groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
505 }
506 }
507
508 final var conversationPartnerAddress = isSync ? destination : source;
509 if (conversationPartnerAddress != null && message.isEndSession()) {
510 account.getSessionStore().deleteAllSessions(conversationPartnerAddress);
511 }
512 if (message.isExpirationUpdate() || message.getBody().isPresent()) {
513 if (message.getGroupContext().isPresent()) {
514 if (message.getGroupContext().get().getGroupV1().isPresent()) {
515 var groupInfo = message.getGroupContext().get().getGroupV1().get();
516 var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
517 if (group != null) {
518 if (group.messageExpirationTime != message.getExpiresInSeconds()) {
519 group.messageExpirationTime = message.getExpiresInSeconds();
520 account.getGroupStore().updateGroup(group);
521 }
522 }
523 } else if (message.getGroupContext().get().getGroupV2().isPresent()) {
524 // disappearing message timer already stored in the DecryptedGroup
525 }
526 } else if (conversationPartnerAddress != null) {
527 contactHelper.setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
528 }
529 }
530 if (!ignoreAttachments) {
531 if (message.getAttachments().isPresent()) {
532 for (var attachment : message.getAttachments().get()) {
533 attachmentHelper.downloadAttachment(attachment);
534 }
535 }
536 if (message.getSharedContacts().isPresent()) {
537 for (var contact : message.getSharedContacts().get()) {
538 if (contact.getAvatar().isPresent()) {
539 attachmentHelper.downloadAttachment(contact.getAvatar().get().getAttachment());
540 }
541 }
542 }
543 if (message.getPreviews().isPresent()) {
544 final var previews = message.getPreviews().get();
545 for (var preview : previews) {
546 if (preview.getImage().isPresent()) {
547 attachmentHelper.downloadAttachment(preview.getImage().get());
548 }
549 }
550 }
551 if (message.getQuote().isPresent()) {
552 final var quote = message.getQuote().get();
553
554 for (var quotedAttachment : quote.getAttachments()) {
555 final var thumbnail = quotedAttachment.getThumbnail();
556 if (thumbnail != null) {
557 attachmentHelper.downloadAttachment(thumbnail);
558 }
559 }
560 }
561 }
562 if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
563 final ProfileKey profileKey;
564 try {
565 profileKey = new ProfileKey(message.getProfileKey().get());
566 } catch (InvalidInputException e) {
567 throw new AssertionError(e);
568 }
569 if (account.getSelfRecipientId().equals(source)) {
570 this.account.setProfileKey(profileKey);
571 }
572 this.account.getProfileStore().storeProfileKey(source, profileKey);
573 }
574 if (message.getSticker().isPresent()) {
575 final var messageSticker = message.getSticker().get();
576 final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
577 var sticker = account.getStickerStore().getSticker(stickerPackId);
578 if (sticker == null) {
579 sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
580 account.getStickerStore().updateSticker(sticker);
581 }
582 jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
583 }
584 return actions;
585 }
586 }