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