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