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