mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-09-20 06:09:21 +00:00
Merge branch 'master' into schemas-on-release
This commit is contained in:
commit
ca3b4de1e7
15
CHANGELOG.md
15
CHANGELOG.md
@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Sending to large groups is no longer slowed down by members that are already known to be unregistered; they are skipped instead of being retried via the legacy 1:1 send path on every send.
|
||||||
|
|
||||||
|
## [0.14.5] - 2026-06-11
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Disable host validation when binding on 0.0.0.0
|
||||||
|
- Use new SVR2 enclave for PINs
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Receiving unidentified sender messages after signal server change
|
||||||
|
|
||||||
## [0.14.4] - 2026-05-23
|
## [0.14.4] - 2026-05-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -5,12 +5,12 @@ plugins {
|
|||||||
application
|
application
|
||||||
eclipse
|
eclipse
|
||||||
`check-lib-versions`
|
`check-lib-versions`
|
||||||
id("org.graalvm.buildtools.native") version "1.0.0"
|
id("org.graalvm.buildtools.native") version "1.1.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "org.asamk"
|
group = "org.asamk"
|
||||||
version = "0.14.5-SNAPSHOT"
|
version = "0.14.6-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|||||||
@ -45,6 +45,9 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.14.5" date="2026-06-11">
|
||||||
|
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.5</url>
|
||||||
|
</release>
|
||||||
<release version="0.14.4" date="2026-05-23">
|
<release version="0.14.4" date="2026-05-23">
|
||||||
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.4</url>
|
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.4</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ slf4j = "2.0.18"
|
|||||||
junit = "6.1.0"
|
junit = "6.1.0"
|
||||||
micronaut-json-schema = "2.0.1"
|
micronaut-json-schema = "2.0.1"
|
||||||
micronaut-core = "5.0.0"
|
micronaut-core = "5.0.0"
|
||||||
signal-service = "2.15.3_unofficial_147"
|
signal-service = "2.15.3_unofficial_148"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
||||||
|
|||||||
@ -30,7 +30,9 @@ class LiveConfig {
|
|||||||
private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT2 = Base64.getDecoder()
|
private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT2 = Base64.getDecoder()
|
||||||
.decode("BUkY0I+9+oPgDCn4+Ac6Iu813yvqkDr/ga8DzLxFxuk6");
|
.decode("BUkY0I+9+oPgDCn4+Ac6Iu813yvqkDr/ga8DzLxFxuk6");
|
||||||
private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
||||||
private static final String SVR2_MRENCLAVE = "29cd63c87bea751e3bfd0fbd401279192e2e5c99948b4ee9437eafc4968355fb";
|
private static final String SVR2_MRENCLAVE = "ced8217b26228e4b210c985786999d095c4958a94faf37b14acaf25c4cbb02a4";
|
||||||
|
private static final String SVR2_MRENCLAVE_LEGACY = "1240acbd4aa26974184844c8a46b1022d3957ac8a76c1fd8f5b1a15141ee0708";
|
||||||
|
private static final String SVR2_MRENCLAVE_OLD_LEGACY = "29cd63c87bea751e3bfd0fbd401279192e2e5c99948b4ee9437eafc4968355fb";
|
||||||
|
|
||||||
private static final String URL = "https://chat.signal.org";
|
private static final String URL = "https://chat.signal.org";
|
||||||
private static final String CDN_URL = "https://cdn.signal.org";
|
private static final String CDN_URL = "https://cdn.signal.org";
|
||||||
@ -93,7 +95,7 @@ class LiveConfig {
|
|||||||
createDefaultServiceConfiguration(interceptors),
|
createDefaultServiceConfiguration(interceptors),
|
||||||
getUnidentifiedSenderTrustRoots(),
|
getUnidentifiedSenderTrustRoots(),
|
||||||
CDSI_MRENCLAVE,
|
CDSI_MRENCLAVE,
|
||||||
List.of(SVR2_MRENCLAVE));
|
List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY, SVR2_MRENCLAVE_OLD_LEGACY));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LiveConfig() {
|
private LiveConfig() {
|
||||||
|
|||||||
@ -31,7 +31,8 @@ public class ServiceConfig {
|
|||||||
public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
|
public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
|
||||||
final var attachmentBackfill = !isPrimaryDevice;
|
final var attachmentBackfill = !isPrimaryDevice;
|
||||||
final var spqr = true;
|
final var spqr = true;
|
||||||
return new AccountAttributes.Capabilities(true, true, attachmentBackfill, spqr);
|
final var usernameSyncChangeMessage = !isPrimaryDevice;
|
||||||
|
return new AccountAttributes.Capabilities(true, true, attachmentBackfill, spqr, usernameSyncChangeMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ServiceEnvironmentConfig getServiceEnvironmentConfig(
|
public static ServiceEnvironmentConfig getServiceEnvironmentConfig(
|
||||||
|
|||||||
@ -30,7 +30,9 @@ class StagingConfig {
|
|||||||
private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT2 = Base64.getDecoder()
|
private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT2 = Base64.getDecoder()
|
||||||
.decode("BYhU6tPjqP46KGZEzRs1OL4U39V5dlPJ/X09ha4rErkm");
|
.decode("BYhU6tPjqP46KGZEzRs1OL4U39V5dlPJ/X09ha4rErkm");
|
||||||
private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
||||||
private static final String SVR2_MRENCLAVE = "a75542d82da9f6914a1e31f8a7407053b99cc99a0e7291d8fbd394253e19b036";
|
private static final String SVR2_MRENCLAVE = "3c699f4975aaa3d172c0aad042f94f031b2b03e10b9c19a45116a01693d83302";
|
||||||
|
private static final String SVR2_MRENCLAVE_LEGACY = "97f151f6ed078edbbfd72fa9cae694dcc08353f1f5e8d9ccd79a971b10ffc535";
|
||||||
|
private static final String SVR2_MRENCLAVE_OLD_LEGACY = "a75542d82da9f6914a1e31f8a7407053b99cc99a0e7291d8fbd394253e19b036";
|
||||||
|
|
||||||
private static final String URL = "https://chat.staging.signal.org";
|
private static final String URL = "https://chat.staging.signal.org";
|
||||||
private static final String CDN_URL = "https://cdn-staging.signal.org";
|
private static final String CDN_URL = "https://cdn-staging.signal.org";
|
||||||
@ -93,7 +95,7 @@ class StagingConfig {
|
|||||||
createDefaultServiceConfiguration(interceptors),
|
createDefaultServiceConfiguration(interceptors),
|
||||||
getUnidentifiedSenderTrustRoots(),
|
getUnidentifiedSenderTrustRoots(),
|
||||||
CDSI_MRENCLAVE,
|
CDSI_MRENCLAVE,
|
||||||
List.of(SVR2_MRENCLAVE));
|
List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY, SVR2_MRENCLAVE_OLD_LEGACY));
|
||||||
}
|
}
|
||||||
|
|
||||||
private StagingConfig() {
|
private StagingConfig() {
|
||||||
|
|||||||
@ -649,7 +649,7 @@ public class CallManager implements AutoCloseable {
|
|||||||
case "busy", "busyonanotherdevice" -> HangupMessage.Type.BUSY;
|
case "busy", "busyonanotherdevice" -> HangupMessage.Type.BUSY;
|
||||||
default -> HangupMessage.Type.NORMAL;
|
default -> HangupMessage.Type.NORMAL;
|
||||||
};
|
};
|
||||||
var hangupMessage = new HangupMessage(state.callId, type, state.deviceId);
|
var hangupMessage = new HangupMessage(state.callId, type, 0);
|
||||||
var callMessage = SignalServiceCallMessage.forHangup(hangupMessage, state.deviceId);
|
var callMessage = SignalServiceCallMessage.forHangup(hangupMessage, state.deviceId);
|
||||||
final var result = context.getSendHelper().sendCallMessage(callMessage, state.recipientId);
|
final var result = context.getSendHelper().sendCallMessage(callMessage, state.recipientId);
|
||||||
logger.debug("Sent hangup ({}) via Signal for call {}", hangupType, callIdUnsigned(state.callId));
|
logger.debug("Sent hangup ({}) via Signal for call {}", hangupType, callIdUnsigned(state.callId));
|
||||||
@ -711,7 +711,7 @@ public class CallManager implements AutoCloseable {
|
|||||||
&& !"rejected".equals(reason)
|
&& !"rejected".equals(reason)
|
||||||
&& !"remote_busy".equals(reason)
|
&& !"remote_busy".equals(reason)
|
||||||
&& !"ringrtc_hangup".equals(reason)) {
|
&& !"ringrtc_hangup".equals(reason)) {
|
||||||
var hangupMessage = new HangupMessage(callId, HangupMessage.Type.NORMAL, state.deviceId);
|
var hangupMessage = new HangupMessage(callId, HangupMessage.Type.NORMAL, 0);
|
||||||
var callMessage = SignalServiceCallMessage.forHangup(hangupMessage, null);
|
var callMessage = SignalServiceCallMessage.forHangup(hangupMessage, null);
|
||||||
final var result = context.getSendHelper().sendCallMessage(callMessage, state.recipientId);
|
final var result = context.getSendHelper().sendCallMessage(callMessage, state.recipientId);
|
||||||
if (!result.isSuccess()) {
|
if (!result.isSuccess()) {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import org.asamk.signal.manager.util.PaymentUtils;
|
|||||||
import org.asamk.signal.manager.util.ProfileUtils;
|
import org.asamk.signal.manager.util.ProfileUtils;
|
||||||
import org.asamk.signal.manager.util.Utils;
|
import org.asamk.signal.manager.util.Utils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.signal.core.util.ExpiringProfileCredentialUtil;
|
||||||
import org.signal.libsignal.protocol.IdentityKey;
|
import org.signal.libsignal.protocol.IdentityKey;
|
||||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||||
@ -32,7 +33,6 @@ import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
|||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||||
import org.whispersystems.signalservice.api.services.ProfileService;
|
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||||
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|||||||
@ -507,9 +507,32 @@ public class SendHelper {
|
|||||||
) throws IOException {
|
) throws IOException {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
final var addressesMap = recipientIds.stream()
|
// Recipients that are already known to be unregistered are skipped here.
|
||||||
|
// Otherwise every group send re-attempts them via the slow legacy 1:1
|
||||||
|
// fan-out, which on large groups can take tens of seconds (and time out).
|
||||||
|
// The unregistered flag is maintained independently by profile/CDS
|
||||||
|
// discovery, which clears it once a recipient registers again, so they
|
||||||
|
// are re-included automatically. An unregisteredFailure result is still
|
||||||
|
// returned for each skipped recipient, so callers see them unchanged.
|
||||||
|
final var skippedResults = new ArrayList<SendMessageResult>();
|
||||||
|
final Set<RecipientId> targetRecipientIds;
|
||||||
|
final var unregisteredRecipientIds = account.getRecipientStore().getUnregisteredRecipientIds(recipientIds);
|
||||||
|
if (unregisteredRecipientIds.isEmpty()) {
|
||||||
|
targetRecipientIds = recipientIds;
|
||||||
|
} else {
|
||||||
|
logger.debug("Skipping {} known-unregistered recipient(s) in group send.",
|
||||||
|
unregisteredRecipientIds.size());
|
||||||
|
targetRecipientIds = new HashSet<>(recipientIds);
|
||||||
|
targetRecipientIds.removeAll(unregisteredRecipientIds);
|
||||||
|
for (final var recipientId : unregisteredRecipientIds) {
|
||||||
|
skippedResults.add(SendMessageResult.unregisteredFailure(context.getRecipientHelper()
|
||||||
|
.resolveSignalServiceAddress(recipientId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var addressesMap = targetRecipientIds.stream()
|
||||||
.collect(Collectors.toMap(id -> id, context.getRecipientHelper()::resolveSignalServiceAddress));
|
.collect(Collectors.toMap(id -> id, context.getRecipientHelper()::resolveSignalServiceAddress));
|
||||||
final var unidentifiedAccessesMap = context.getUnidentifiedAccessHelper().getAccessFor(recipientIds);
|
final var unidentifiedAccessesMap = context.getUnidentifiedAccessHelper().getAccessFor(targetRecipientIds);
|
||||||
final var groupSendEndorsementsResult = getGroupSendEndorsements(groupInfo);
|
final var groupSendEndorsementsResult = getGroupSendEndorsements(groupInfo);
|
||||||
final var groupSecretParams = groupInfo instanceof GroupInfoV2 gv2
|
final var groupSecretParams = groupInfo instanceof GroupInfoV2 gv2
|
||||||
? GroupSecretParams.deriveFromMasterKey((gv2.getMasterKey()))
|
? GroupSecretParams.deriveFromMasterKey((gv2.getMasterKey()))
|
||||||
@ -523,7 +546,7 @@ public class SendHelper {
|
|||||||
: groupSendEndorsementsResult.first();
|
: groupSendEndorsementsResult.first();
|
||||||
Set<RecipientId> senderKeyTargets = groupInfo.getDistributionId() == null || groupSendEndorsements == null
|
Set<RecipientId> senderKeyTargets = groupInfo.getDistributionId() == null || groupSendEndorsements == null
|
||||||
? Set.of()
|
? Set.of()
|
||||||
: recipientIds.stream()
|
: targetRecipientIds.stream()
|
||||||
.filter(s -> this.isSenderKeyCapable(s,
|
.filter(s -> this.isSenderKeyCapable(s,
|
||||||
addressesMap.get(s),
|
addressesMap.get(s),
|
||||||
unidentifiedAccessesMap.get(s),
|
unidentifiedAccessesMap.get(s),
|
||||||
@ -533,7 +556,7 @@ public class SendHelper {
|
|||||||
logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size());
|
logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size());
|
||||||
senderKeyTargets = Set.of();
|
senderKeyTargets = Set.of();
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Can use sender key for {}/{} recipients.", senderKeyTargets.size(), recipientIds.size());
|
logger.debug("Can use sender key for {}/{} recipients.", senderKeyTargets.size(), targetRecipientIds.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
final var allResults = new ArrayList<SendMessageResult>(recipientIds.size());
|
final var allResults = new ArrayList<SendMessageResult>(recipientIds.size());
|
||||||
@ -569,9 +592,9 @@ public class SendHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final var legacyTargets = new HashSet<>(recipientIds);
|
final var legacyTargets = new HashSet<>(targetRecipientIds);
|
||||||
legacyTargets.removeAll(senderKeyTargets);
|
legacyTargets.removeAll(senderKeyTargets);
|
||||||
final boolean onlyTargetIsSelfWithLinkedDevice = recipientIds.isEmpty() && account.isMultiDevice();
|
final boolean onlyTargetIsSelfWithLinkedDevice = targetRecipientIds.isEmpty() && account.isMultiDevice();
|
||||||
|
|
||||||
if (!legacyTargets.isEmpty() || onlyTargetIsSelfWithLinkedDevice) {
|
if (!legacyTargets.isEmpty() || onlyTargetIsSelfWithLinkedDevice) {
|
||||||
if (!legacyTargets.isEmpty()) {
|
if (!legacyTargets.isEmpty()) {
|
||||||
@ -605,6 +628,7 @@ public class SendHelper {
|
|||||||
isRecipientUpdate || !allResults.isEmpty());
|
isRecipientUpdate || !allResults.isEmpty());
|
||||||
allResults.addAll(results);
|
allResults.addAll(results);
|
||||||
}
|
}
|
||||||
|
allResults.addAll(skippedResults);
|
||||||
final var duration = Duration.ofMillis(System.currentTimeMillis() - startTime);
|
final var duration = Duration.ofMillis(System.currentTimeMillis() - startTime);
|
||||||
logger.debug("Sending took {}", duration.toString());
|
logger.debug("Sending took {}", duration.toString());
|
||||||
return allResults;
|
return allResults;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package org.asamk.signal.manager.internal;
|
|||||||
import org.asamk.signal.manager.config.ServiceConfig;
|
import org.asamk.signal.manager.config.ServiceConfig;
|
||||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||||
import org.asamk.signal.manager.util.Utils;
|
import org.asamk.signal.manager.util.Utils;
|
||||||
|
import org.signal.core.util.UptimeSleepTimer;
|
||||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||||
import org.signal.libsignal.net.Network;
|
import org.signal.libsignal.net.Network;
|
||||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||||
@ -40,7 +41,6 @@ import org.whispersystems.signalservice.api.services.ProfileService;
|
|||||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
|
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
|
||||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
|
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
|
||||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
|
||||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
||||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||||
import org.whispersystems.signalservice.internal.websocket.LibSignalChatConnection;
|
import org.whispersystems.signalservice.internal.websocket.LibSignalChatConnection;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
package org.asamk.signal.manager.internal;
|
package org.asamk.signal.manager.internal;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.signal.core.util.SleepTimer;
|
||||||
import org.signal.network.util.Preconditions;
|
import org.signal.network.util.Preconditions;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.signalservice.api.util.SleepTimer;
|
|
||||||
import org.whispersystems.signalservice.api.websocket.HealthMonitor;
|
import org.whispersystems.signalservice.api.websocket.HealthMonitor;
|
||||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
||||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
||||||
|
|||||||
@ -463,6 +463,40 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the subset of the given recipients that are currently known to be
|
||||||
|
* unregistered (i.e. have an unregistered timestamp set).
|
||||||
|
* <p>
|
||||||
|
* These can be skipped when sending group messages; otherwise every send
|
||||||
|
* re-attempts them via the slow legacy 1:1 fan-out. The unregistered flag is
|
||||||
|
* maintained independently by profile/CDS discovery and cleared again once a
|
||||||
|
* recipient registers, so they are re-included automatically.
|
||||||
|
*/
|
||||||
|
public Set<RecipientId> getUnregisteredRecipientIds(final Set<RecipientId> recipientIds) {
|
||||||
|
if (recipientIds.isEmpty()) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
final var recipientIdsCommaSeparated = recipientIds.stream()
|
||||||
|
.map(recipientId -> String.valueOf(recipientId.id()))
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
SELECT r._id
|
||||||
|
FROM %s r
|
||||||
|
WHERE r.unregistered_timestamp IS NOT NULL AND r._id IN (%s)
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_RECIPIENT, recipientIdsCommaSeparated);
|
||||||
|
try (final var connection = database.getConnection()) {
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
try (var result = Utils.executeQueryForStream(statement, this::getRecipientIdFromResultSet)) {
|
||||||
|
return result.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed read from recipient store", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Set<String> getAllNumbers() {
|
public Set<String> getAllNumbers() {
|
||||||
final var sql = (
|
final var sql = (
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
0.94.1
|
0.94.4
|
||||||
|
|||||||
@ -8,7 +8,7 @@ public class BaseConfig {
|
|||||||
public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
|
public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
|
||||||
|
|
||||||
static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
|
static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
|
||||||
.orElse("Signal-Android/8.12.1");
|
.orElse("Signal-Android/8.15.0");
|
||||||
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
|
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
|
||||||
? "signal-cli"
|
? "signal-cli"
|
||||||
: PROJECT_NAME + "/" + PROJECT_VERSION;
|
: PROJECT_NAME + "/" + PROJECT_VERSION;
|
||||||
|
|||||||
@ -310,6 +310,8 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
|||||||
if (callMessage.hangup().isPresent()) {
|
if (callMessage.hangup().isPresent()) {
|
||||||
var hangupMessage = callMessage.hangup().get();
|
var hangupMessage = callMessage.hangup().get();
|
||||||
writer.println("Hangup message: {}", callIdUnsigned(hangupMessage.id()));
|
writer.println("Hangup message: {}", callIdUnsigned(hangupMessage.id()));
|
||||||
|
writer.println("- type: {}", hangupMessage.type());
|
||||||
|
writer.println("- device id: {}", hangupMessage.deviceId());
|
||||||
}
|
}
|
||||||
if (!callMessage.iceUpdate().isEmpty()) {
|
if (!callMessage.iceUpdate().isEmpty()) {
|
||||||
writer.println("Ice update messages:");
|
writer.println("Ice update messages:");
|
||||||
|
|||||||
@ -73,7 +73,13 @@ public class HttpServerHandler implements AutoCloseable {
|
|||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
logger.info("Started HTTP server on {}", address);
|
logger.info("Started HTTP server on {}", address);
|
||||||
logger.warn("HTTP server has no authentication; Host header is pinned to {}", allowedHosts);
|
// If we're listening on any local address (0.0.0.0 or ::), skip Host header validation
|
||||||
|
final var addr = address == null ? null : address.getAddress();
|
||||||
|
if (addr != null && addr.isAnyLocalAddress()) {
|
||||||
|
logger.warn("HTTP server has no authentication; Host header validation DISABLED because listening on {}", address);
|
||||||
|
} else {
|
||||||
|
logger.warn("HTTP server has no authentication; Host header is pinned to {}", allowedHosts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -306,6 +312,18 @@ public class HttpServerHandler implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isHostAllowed(final HttpExchange httpExchange) {
|
private boolean isHostAllowed(final HttpExchange httpExchange) {
|
||||||
|
// If the server is bound to any local address (0.0.0.0 or ::), skip host header validation
|
||||||
|
if (address != null) {
|
||||||
|
final var addr = address.getAddress();
|
||||||
|
if (addr != null && addr.isAnyLocalAddress()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final var hostStr = address.getHostString();
|
||||||
|
if ("0.0.0.0".equals(hostStr) || "::".equals(hostStr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final var hostHeader = httpExchange.getRequestHeaders().getFirst("Host");
|
final var hostHeader = httpExchange.getRequestHeaders().getFirst("Host");
|
||||||
if (hostHeader == null || hostHeader.isEmpty()) {
|
if (hostHeader == null || hostHeader.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -776,6 +776,20 @@
|
|||||||
"type": "java.lang.Class",
|
"type": "java.lang.Class",
|
||||||
"jniAccessible": true,
|
"jniAccessible": true,
|
||||||
"methods": [
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "forName",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.String"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "forName",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.String",
|
||||||
|
"boolean",
|
||||||
|
"java.lang.ClassLoader"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getCanonicalName",
|
"name": "getCanonicalName",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
@ -784,6 +798,10 @@
|
|||||||
"name": "getClassLoader",
|
"name": "getClassLoader",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getName",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getPermittedSubclasses",
|
"name": "getPermittedSubclasses",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
@ -819,7 +837,27 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "java.lang.ClassNotFoundException"
|
"type": "java.lang.ClassNotFoundException",
|
||||||
|
"jniAccessible": true,
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.String"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.String",
|
||||||
|
"java.lang.Throwable"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "java.lang.ClassValue"
|
"type": "java.lang.ClassValue"
|
||||||
@ -959,6 +997,18 @@
|
|||||||
"name": "currentThread",
|
"name": "currentThread",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getContextClassLoader",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getId",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getName",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getStackTrace",
|
"name": "getStackTrace",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
@ -966,6 +1016,18 @@
|
|||||||
{
|
{
|
||||||
"name": "isVirtual",
|
"name": "isVirtual",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setContextClassLoader",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.ClassLoader"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setName",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.String"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -979,10 +1041,18 @@
|
|||||||
"java.lang.Throwable"
|
"java.lang.Throwable"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getCause",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getMessage",
|
"name": "getMessage",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getStackTrace",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getSuppressed",
|
"name": "getSuppressed",
|
||||||
"parameterTypes": []
|
"parameterTypes": []
|
||||||
@ -1586,7 +1656,42 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "kotlin.reflect.jvm.internal.impl.resolve.scopes.DescriptorKindFilter",
|
"type": "kotlin.reflect.jvm.internal.impl.resolve.scopes.DescriptorKindFilter",
|
||||||
"allPublicFields": true
|
"allPublicFields": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "ALL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CALLABLES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CLASSIFIERS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Companion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FUNCTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NON_SINGLETON_CLASSIFIERS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PACKAGES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SINGLETON_CLASSIFIERS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TYPE_ALIASES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VALUES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VARIABLES"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "kotlinx.coroutines.AwaitAll"
|
"type": "kotlinx.coroutines.AwaitAll"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user