@@ -32,6 +32,7 @@ maxChallenges = 10000
32maxSessions = 10000
32maxSessions = 10000
33maxAckedIds = 10000
33maxAckedIds = 10000
34maxAckOverflow = 200
34maxAckOverflow = 200
35+maxDeliveredIds = 10000
35maxChallengesPerKey = 5
36maxChallengesPerKey = 5
36maxSessionsPerKey = 10
37maxSessionsPerKey = 10
37challengeExpiry : Int<Ms>
38challengeExpiry : Int<Ms>
@@ -132,6 +133,7 @@ type Message = {messageId: MessageIdHex, toKey: PubkeyHex, encryptedBlob: BlobHe
132type PresenceEntry = {pubkey: PubkeyHex, server: AnyServerName, expiresAt: Timestamp, timestamp: Timestamp}
133type PresenceEntry = {pubkey: PubkeyHex, server: AnyServerName, expiresAt: Timestamp, timestamp: Timestamp}
133type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp}
134type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp}
134type AckedEntry = {messageId: MessageIdHex, toKey: PubkeyHex, ackedAt: Timestamp, originServer: ServerName}
135type AckedEntry = {messageId: MessageIdHex, toKey: PubkeyHex, ackedAt: Timestamp, originServer: ServerName}
136+type DeliveredEntry = {messageId: MessageIdHex, toKey: PubkeyHex, deliveredAt: Timestamp}
135type ForwardFailure = {server: Text, failedAt: Timestamp, failures: Int}
137type ForwardFailure = {server: Text, failedAt: Timestamp, failures: Int}
136type SendRateEntry = {pubkey: PubkeyHex, windowStart: Timestamp, count: Int}
138type SendRateEntry = {pubkey: PubkeyHex, windowStart: Timestamp, count: Int}
137type ChallengeRateEntry = {pubkey: PubkeyHex, windowStart: Timestamp, count: Int}
139type ChallengeRateEntry = {pubkey: PubkeyHex, windowStart: Timestamp, count: Int}
@@ -191,6 +193,8 @@ data ForwardResult = ForwardDuplicate {} | ForwardRejected {} | ForwardStored {}
192*ackOverflow : [AckedEntry]
194*ackOverflow : [AckedEntry]
196+*deliveredIds : [DeliveredEntry]
194-- ============================================
198-- ============================================
196-- ============================================
200-- ============================================
@@ -420,6 +424,13 @@ matchesHostnameOrSubdomain : Text -> Text -> Bool
420matchesHostnameOrSubdomain = \sn name ->
424matchesHostnameOrSubdomain = \sn name ->
421 matchesHostname sn name || containsSuffix ("." ++ name) sn
425 matchesHostname sn name || containsSuffix ("." ++ name) sn
427+hasDotBeforePort : Text -> Bool
428+hasDotBeforePort = \s ->
429+ if s == "" then False {}
430+ else if take 1 s == ":" then False {}
431+ else if take 1 s == "." then True {}
432+ else hasDotBeforePort (drop 1 s)
423isBadServerName : Text -> Bool
434isBadServerName : Text -> Bool
424isBadServerName = \sn ->
435isBadServerName = \sn ->
@@ -448,6 +459,8 @@ isBadServerName = \sn ->
448 || hasHexPrefix sn
459 || hasHexPrefix sn
449 || (isDigitChar (take 1 sn) && isAllDigitsOrDotsUntilColonOrEnd sn)
460 || (isDigitChar (take 1 sn) && isAllDigitsOrDotsUntilColonOrEnd sn)
450 || isHighOctetPrefix sn
461 || isHighOctetPrefix sn
462+ || not (hasDotBeforePort sn)
463+ || containsSuffix ".arpa" sn
452authenticateSession : [Session] -> Text -> Timestamp -> AuthResult
465authenticateSession : [Session] -> Text -> Timestamp -> AuthResult
453authenticateSession = \sessions token t ->
466authenticateSession = \sessions token t ->
@@ -658,6 +671,8 @@ pruneExpired = \t -> do
658 set *activePolls = do p <- activePolls; where t - p.startedAt < pollTimeoutMs * 3; yield p
671 set *activePolls = do p <- activePolls; where t - p.startedAt < pollTimeoutMs * 3; yield p
659 ackOverflow <- *ackOverflow
672 ackOverflow <- *ackOverflow
660 set *ackOverflow = do a <- ackOverflow; where a.ackedAt > messageCutoff; yield a
673 set *ackOverflow = do a <- ackOverflow; where a.ackedAt > messageCutoff; yield a
674+ deliveredIds <- *deliveredIds
675+ set *deliveredIds = do d <- deliveredIds; where d.deliveredAt > messageCutoff; yield d
661 yield {expiredLocal: expiredLocalPresence presenceBefore presenceAfter t, peerServers: peerServersBefore}
676 yield {expiredLocal: expiredLocalPresence presenceBefore presenceAfter t, peerServers: peerServersBefore}
663maybePrune = \t -> do
678maybePrune = \t -> do
@@ -868,9 +883,18 @@ retryEvictedAcks = \evictedEntries pendingAcks ->
868 let overflowExcess = count updated - maxAckOverflow
883 let overflowExcess = count updated - maxAckOverflow
869 if overflowExcess <= 0
884 if overflowExcess <= 0
870 then do set *ackOverflow = updated; yield 0
885 then do set *ackOverflow = updated; yield 0
871− else do set *ackOverflow = evictOldestAckedIds updated overflowExcess; yield overflowExcess
886+ else do
887+ let oldest = takeRelation overflowExcess (sortBy (\a -> a.ackedAt) updated)
888+ set *ackOverflow = diff updated oldest
889+ deliveredIds <- *deliveredIds
890+ let newDelivered = do a <- oldest; yield {messageId: a.messageId, toKey: a.toKey, deliveredAt: a.ackedAt}
891+ let combinedDelivered = union deliveredIds newDelivered
892+ let deliveredExcess = count combinedDelivered - maxDeliveredIds
893+ set *deliveredIds = if deliveredExcess <= 0 then combinedDelivered
894+ else diff combinedDelivered (takeRelation deliveredExcess (sortBy (\d -> d.deliveredAt) combinedDelivered))
895+ yield overflowExcess
872 when (dropped > 0)
896 when (dropped > 0)
873− (println ("ackMessages: overflow at capacity (" ++ show maxAckOverflow ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server)))
897+ (println ("ackMessages: overflow at capacity (" ++ show maxAckOverflow ++ "), " ++ show dropped ++ " ack entries moved to delivered set for " ++ server)))
874 Ok _ -> yield {})
898 Ok _ -> yield {})
876retryOverflowAcks = \overflow ->
900retryOverflowAcks = \overflow ->
@@ -1089,6 +1113,7 @@ forwardCandidates = \targets messages forwards t ->
1089 union acc (do
1113 union acc (do
1090 m <- messages
1114 m <- messages
1091 where m.toKey == target.pubkey
1115 where m.toKey == target.pubkey
1116+ where m.originServer == ""
1092 where none serverFwdIds (\fid -> fid == m.messageId)
1117 where none serverFwdIds (\fid -> fid == m.messageId)
1093 yield {messageId: m.messageId, toServer: target.server, createdAt: t,
1118 yield {messageId: m.messageId, toServer: target.server, createdAt: t,
1094 toKey: m.toKey, encryptedBlob: m.encryptedBlob, timestamp: m.timestamp})) [] targets
1119 toKey: m.toKey, encryptedBlob: m.encryptedBlob, timestamp: m.timestamp})) [] targets
@@ -1155,24 +1180,27 @@ serve = \req -> case req of
1155 challenges <- *challenges
1180 challenges <- *challenges
1156 let active = do c <- challenges; where c.expiresAt > t; yield c
1181 let active = do c <- challenges; where c.expiresAt > t; yield c
1157 let activeForKey = count (do c <- active; where c.pubkey == pubkey; yield c)
1182 let activeForKey = count (do c <- active; where c.pubkey == pubkey; yield c)
1158− if activeForKey >= maxChallengesPerKey
1183+ let cappedActive = if activeForKey >= maxChallengesPerKey
1184+ then let forKey = do c <- active; where c.pubkey == pubkey; yield c in
1185+ let others = do c <- active; where c.pubkey != pubkey; yield c in
1186+ union others (evictOneOldest forKey (\c -> c.expiresAt))
1188+ if count cappedActive >= maxChallenges
1159 then yield {pr: pr, accepted: False {}, rateLimited: False {}}
1189 then yield {pr: pr, accepted: False {}, rateLimited: False {}}
1160− else if count active >= maxChallenges
1190+ else do
1161− then yield {pr: pr, accepted: False {}, rateLimited: False {}}
1163− challengeRates <- *challengeRates
1191+ challengeRates <- *challengeRates
1164− let senderEntry = headOf (do r <- challengeRates; where r.pubkey == pubkey; yield r)
1192+ let senderEntry = headOf (do r <- challengeRates; where r.pubkey == pubkey; yield r)
1165− let isNewWindow = case senderEntry of Nothing {} -> True {}; Just {value: r} -> t - r.windowStart >= challengeRateWindowMs
1193+ let isNewWindow = case senderEntry of Nothing {} -> True {}; Just {value: r} -> t - r.windowStart >= challengeRateWindowMs
1166− let rateCount = case senderEntry of Nothing {} -> 0; Just {value: r} -> if isNewWindow then 0 else r.count
1194+ let rateCount = case senderEntry of Nothing {} -> 0; Just {value: r} -> if isNewWindow then 0 else r.count
1167− if rateCount >= maxChallengeRatePerKey
1195+ if rateCount >= maxChallengeRatePerKey
1168− then yield {pr: pr, accepted: False {}, rateLimited: True {}}
1196+ then yield {pr: pr, accepted: False {}, rateLimited: True {}}
1169− else do
1197+ else do
1170− let newRates = if isNewWindow
1198+ let newRates = if isNewWindow
1171− then union (do r <- challengeRates; where r.pubkey != pubkey; yield r) [{pubkey: pubkey, windowStart: t, count: 1}]
1199+ then union (do r <- challengeRates; where r.pubkey != pubkey; yield r) [{pubkey: pubkey, windowStart: t, count: 1}]
1172− else do r <- challengeRates; yield (if r.pubkey == pubkey then {pubkey: r.pubkey, windowStart: r.windowStart, count: r.count + 1} else r)
1200+ else do r <- challengeRates; yield (if r.pubkey == pubkey then {pubkey: r.pubkey, windowStart: r.windowStart, count: r.count + 1} else r)
1173− set *challengeRates = if count newRates > maxChallengeRateEntries then (do r <- newRates; where t - r.windowStart < challengeRateWindowMs; yield r) else newRates
1201+ set *challengeRates = if count newRates > maxChallengeRateEntries then (do r <- newRates; where t - r.windowStart < challengeRateWindowMs; yield r) else newRates
1174− set *challenges = union active [{pubkey: pubkey, challenge: challenge, expiresAt: t + challengeExpiry}]
1202+ set *challenges = union cappedActive [{pubkey: pubkey, challenge: challenge, expiresAt: t + challengeExpiry}]
1175− yield {pr: pr, accepted: True {}, rateLimited: False {}}
1203+ yield {pr: pr, accepted: True {}, rateLimited: False {}}
1176 when result.rateLimited
1204 when result.rateLimited
1177 (println ("challenge: rate limited (" ++ show maxChallengeRatePerKey ++ "/s per key), rejecting for " ++ pubkey))
1205 (println ("challenge: rate limited (" ++ show maxChallengeRatePerKey ++ "/s per key), rejecting for " ++ pubkey))
1178 when (not result.accepted && not result.rateLimited)
1206 when (not result.accepted && not result.rateLimited)
@@ -1183,16 +1211,19 @@ serve = \req -> case req of
1183 VerifyAuth {pubkey, challenge, signature, respond} -> do
1211 VerifyAuth {pubkey, challenge, signature, respond} -> do
1184 t <- now
1212 t <- now
1185 let expiry = t + sessionExpiry
1213 let expiry = t + sessionExpiry
1214+ let sigValid = verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
1186 validation <- atomic do
1215 validation <- atomic do
1187 pr <- maybePrune t
1216 pr <- maybePrune t
1188− challenges <- *challenges
1189− let hasMatch = challengeMatch challenges pubkey challenge t
1190− let sigValid = hasMatch && verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
1191 if not sigValid
1217 if not sigValid
1192 then yield {pr: pr, valid: False {}}
1218 then yield {pr: pr, valid: False {}}
1220+ challenges <- *challenges
1221+ let hasMatch = challengeMatch challenges pubkey challenge t
1223+ then yield {pr: pr, valid: False {}}
1194− set *challenges = challengesExcluding challenges pubkey challenge
1225+ set *challenges = challengesExcluding challenges pubkey challenge
1195− yield {pr: pr, valid: True {}}
1226+ yield {pr: pr, valid: True {}}
1196 if validation.valid
1227 if validation.valid
1198 token <- genToken
1229 token <- genToken
@@ -1240,7 +1271,8 @@ serve = \req -> case req of
1240 (\senderKey -> do
1271 (\senderKey -> do
1241 messages <- *messages
1272 messages <- *messages
1242 ackedIds <- *ackedIds
1273 ackedIds <- *ackedIds
1274+ deliveredIds <- *deliveredIds
1243− if messageId != "" && (hasMessage messageId to messages || hasMessage messageId to ackedIds)
1275+ if messageId != "" && (hasMessage messageId to messages || hasMessage messageId to ackedIds || hasMessage messageId to deliveredIds)
1244 then yield {pr: pr, send: SendDuplicate {}}
1276 then yield {pr: pr, send: SendDuplicate {}}
1246 sendRates <- *sendRates
1278 sendRates <- *sendRates
@@ -1346,7 +1378,8 @@ serve = \req -> case req of
1346 pr <- maybePrune t
1378 pr <- maybePrune t
1347 messages <- *messages
1379 messages <- *messages
1348 ackedIds <- *ackedIds
1380 ackedIds <- *ackedIds
1381+ deliveredIds <- *deliveredIds
1349− if hasMessage messageId toKey messages || hasMessage messageId toKey ackedIds
1382+ if hasMessage messageId toKey messages || hasMessage messageId toKey ackedIds || hasMessage messageId toKey deliveredIds
1350 then yield {pr: pr, forward: ForwardDuplicate {}}
1383 then yield {pr: pr, forward: ForwardDuplicate {}}
1351 else if countMessagesFor messages toKey t >= maxMessagesPerRecipient
1384 else if countMessagesFor messages toKey t >= maxMessagesPerRecipient
1352 then yield {pr: pr, forward: ForwardRejected {}}
1385 then yield {pr: pr, forward: ForwardRejected {}}
@@ -1375,7 +1408,17 @@ serve = \req -> case req of
1375 pruneResult <- atomic do
1408 pruneResult <- atomic do
1376 pr <- maybePrune t
1409 pr <- maybePrune t
1377 forwards <- *forwards
1410 forwards <- *forwards
1411+ let ackedForwards = do f <- forwards; where f.toServer == fromServer && any dedupedIds (\mid -> f.messageId == mid); yield f
1378− set *forwards = forwardsExcluding forwards fromServer dedupedIds
1412+ let updatedForwards = forwardsExcluding forwards fromServer dedupedIds
1413+ set *forwards = updatedForwards
1414+ messages <- *messages
1415+ presence <- *presence
1418+ where none updatedForwards (\rf -> rf.messageId == f.messageId && rf.toKey == f.toKey)
1419+ where none presence (\p -> p.pubkey == f.toKey && isLocalServer p.server && p.expiresAt > t)
1421+ set *messages = do m <- messages; where none removable (\f -> m.messageId == f.messageId && m.toKey == f.toKey); yield m
1379 yield pr
1422 yield pr
1380 forkBroadcastOffline t pruneResult
1423 forkBroadcastOffline t pruneResult
1381 yield (respond {ok: True {}}))
1424 yield (respond {ok: True {}}))