on master help sign in
Fix timing side-channel, poll pagination, gossip capacity, federation ack scoping, error logging, and first-hop validation
715d1e2iko
server.knot+162−54
@@ -16,6 +16,8 @@ maxBlobLen = 65536
16maxSignatureLen = 256
16maxSignatureLen = 256
17maxMessageIds = 100
17maxMessageIds = 100
18maxGossipEvents = 100
18maxGossipEvents = 100
19+maxPollEvents = 50
20+maxPresenceEntries = 10000
19clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
21clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
20
22
21-- ============================================
23-- ============================================
@@ -40,6 +42,8 @@ type GossipEvent = {eventType: Text, pubkey: Text, server: Text, timestamp: Int,
40
42
41*seqCounter : Int
43*seqCounter : Int
42
44
45+*forwards : [{messageId: Text, toServer: Text, createdAt: Int}]
46+
43-- ============================================
47-- ============================================
44-- Routes
48-- Routes
45-- ============================================
49-- ============================================
@@ -110,10 +114,21 @@ extractToken = \authHeader ->
110 else ""
114 else ""
111
115
112verifyFederationAuth = \authorization ->
116verifyFederationAuth = \authorization ->
113− extractToken authorization == federationSecret && not (federationSecret == "")
117+ not (federationSecret == "") && safeCompare (extractToken authorization) federationSecret
114
118
115clampTtl = \ttl -> if ttl < 1 then 1 else if ttl > maxPresenceTtl then maxPresenceTtl else ttl
119clampTtl = \ttl -> if ttl < 1 then 1 else if ttl > maxPresenceTtl then maxPresenceTtl else ttl
116
120
121+safeCompareLoop = \s1 s2 result ->
122+ if s1 == "" && s2 == ""
123+ then result
124+ else if s1 == ""
125+ then safeCompareLoop "" (drop 1 s2) (result + 1)
126+ else if s2 == ""
127+ then safeCompareLoop (drop 1 s1) "" (result + 1)
128+ else safeCompareLoop (drop 1 s1) (drop 1 s2) (result + (if take 1 s1 == take 1 s2 then 0 else 1))
129+
130+safeCompare = \a b -> safeCompareLoop a b 0 == 0
131+
117sendToEach = \servers action ->
132sendToEach = \servers action ->
118 if count servers == 0
133 if count servers == 0
119 then yield {}
134 then yield {}
@@ -123,7 +138,9 @@ sendToEach = \servers action ->
123 if server == ""
138 if server == ""
124 then yield {}
139 then yield {}
125 else do
140 else do
126− action server
141+ catch (action server) (\err -> do
142+ println ("federation: error reaching " ++ server ++ ": " ++ show err)
143+ yield {})
127 sendToEach rest action
144 sendToEach rest action
128
145
129pruneExpired = \t -> do
146pruneExpired = \t -> do
@@ -152,6 +169,11 @@ pruneExpired = \t -> do
152 m <- messages
169 m <- messages
153 where m.receivedAt >= t - messageMaxAge
170 where m.receivedAt >= t - messageMaxAge
154 yield m
171 yield m
172+ forwards <- *forwards
173+ set *forwards = do
174+ f <- forwards
175+ where f.createdAt >= t - messageMaxAge
176+ yield f
155 yield {expiredLocal: (do
177 yield {expiredLocal: (do
156 p <- presenceBefore
178 p <- presenceBefore
157 where p.expiresAt <= t
179 where p.expiresAt <= t
@@ -193,12 +215,21 @@ serve = \req -> case req of
193 pruneResult <- atomic do
215 pruneResult <- atomic do
194 pr <- pruneExpired t
216 pr <- pruneExpired t
195 challenges <- *challenges
217 challenges <- *challenges
218+ let existingCount = count (do c <- challenges; where c.pubkey == pubkey; yield c)
219+ let oldestChallengeExpiry = fold (\acc c2 -> if c2.pubkey == pubkey && (acc == -1 || c2.expiresAt < acc) then c2.expiresAt else acc) -1 challenges
220+ let cappedChallenges = if existingCount >= 5 && oldestChallengeExpiry >= 0
221+ then fold (\acc c ->
222+ if not acc.removed && c.pubkey == pubkey && c.expiresAt == oldestChallengeExpiry
223+ then {removed: True {}, items: acc.items}
224+ else {removed: acc.removed, items: union acc.items [c]}) {removed: False {}, items: []} challenges
225+ else {removed: False {}, items: challenges}
196− set *challenges = union challenges
226+ set *challenges = union
227+ cappedChallenges.items
197 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
228 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
198 yield pr
229 yield pr
230+ respond {challenge: challenge}
199 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
231 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
200− else yield {}
201− respond {challenge: (if valid then challenge else "")}
232+ else respond {challenge: ""}
202
233
203 VerifyAuth {pubkey, challenge, signature, respond} -> do
234 VerifyAuth {pubkey, challenge, signature, respond} -> do
204 t <- now
235 t <- now
@@ -233,24 +264,25 @@ serve = \req -> case req of
233 where s.pubkey == pubkey
264 where s.pubkey == pubkey
234 where s.expiresAt > t
265 where s.expiresAt > t
235 yield s
266 yield s
236− let oldestExpiry = fold (\acc s -> if s.expiresAt < acc then s.expiresAt else acc) expiry existingForKey
267+ let oldestExpiry = fold (\acc s -> if acc == -1 || s.expiresAt < acc then s.expiresAt else acc) -1 existingForKey
237 let cappedFold = fold (\acc s ->
268 let cappedFold = fold (\acc s ->
238 if not acc.removed && s.pubkey == pubkey && s.expiresAt == oldestExpiry
269 if not acc.removed && s.pubkey == pubkey && s.expiresAt == oldestExpiry
239 then {removed: True {}, sessions: acc.sessions}
270 then {removed: True {}, sessions: acc.sessions}
240 else {removed: acc.removed, sessions: union acc.sessions [s]}) {removed: False {}, sessions: []} sessions
271 else {removed: acc.removed, sessions: union acc.sessions [s]}) {removed: False {}, sessions: []} sessions
241− let sessionsCapped = if count existingForKey >= 10
272+ let sessionsCapped = if count existingForKey >= 10 && oldestExpiry >= 0
242 then cappedFold.sessions
273 then cappedFold.sessions
243 else sessions
274 else sessions
244 set *sessions = if sigValid
275 set *sessions = if sigValid
245 then union sessionsCapped [{token: token, pubkey: pubkey, expiresAt: expiry}]
276 then union sessionsCapped [{token: token, pubkey: pubkey, expiresAt: expiry}]
246 else sessions
277 else sessions
247− let updatedPresence = if sigValid
278+ let presenceWithout = do
248− then union
249− (do
250 p <- presence
279 p <- presence
251 where not (p.pubkey == pubkey && p.server == serverHost)
280 where not (p.pubkey == pubkey && p.server == serverHost)
252− yield p)
281+ yield p
282+ let updatedPresence = if sigValid
283+ then if count presenceWithout < maxPresenceEntries
253− [{pubkey: pubkey, server: serverHost, expiresAt: expiry, timestamp: t}]
284+ then union presenceWithout [{pubkey: pubkey, server: serverHost, expiresAt: expiry, timestamp: t}]
285+ else presenceWithout
254 else presence
286 else presence
255 set *presence = updatedPresence
287 set *presence = updatedPresence
256 let peerServers = if sigValid && not (federationSecret == "")
288 let peerServers = if sigValid && not (federationSecret == "")
@@ -260,6 +292,9 @@ serve = \req -> case req of
260 else union acc [p.server]) [] updatedPresence
292 else union acc [p.server]) [] updatedPresence
261 else []
293 else []
262 yield {sigValid: sigValid, peerServers: peerServers, expiredLocal: pr.expiredLocal, offlinePeers: pr.peerServers}
294 yield {sigValid: sigValid, peerServers: peerServers, expiredLocal: pr.expiredLocal, offlinePeers: pr.peerServers}
295+ if result.sigValid
296+ then respond {token: token, expiresAt: expiry}
297+ else respond {token: "", expiresAt: 0}
263 if result.sigValid && not (federationSecret == "")
298 if result.sigValid && not (federationSecret == "")
264 then sendToEach result.peerServers (\server ->
299 then sendToEach result.peerServers (\server ->
265 fetch (federationScheme ++ server) (RecvGossip {
300 fetch (federationScheme ++ server) (RecvGossip {
@@ -270,9 +305,6 @@ serve = \req -> case req of
270 p <- result.expiredLocal
305 p <- result.expiredLocal
271 where not (p.pubkey == pubkey)
306 where not (p.pubkey == pubkey)
272 yield p)
307 yield p)
273− if result.sigValid
274− then respond {token: token, expiresAt: expiry}
275− else respond {token: "", expiresAt: 0}
276
308
277 Poll {cursor, authorization, respond} -> do
309 Poll {cursor, authorization, respond} -> do
278 t <- now
310 t <- now
@@ -283,26 +315,36 @@ serve = \req -> case req of
283 messages <- *messages
315 messages <- *messages
284 seqCounter <- *seqCounter
316 seqCounter <- *seqCounter
285 let session = lookupSession sessions token t
317 let session = lookupSession sessions token t
318+ let clampedCursor = if cursor < 0 then 0 else if cursor > seqCounter then seqCounter else cursor
286− let evts = case session of
319+ let matchingMsgs = case session of
287 Nothing {} -> []
320 Nothing {} -> []
288 Just {value: s} -> do
321 Just {value: s} -> do
289 m <- messages
322 m <- messages
290 where m.toKey == s.pubkey
323 where m.toKey == s.pubkey
291 where m.receivedAt >= t - messageMaxAge
324 where m.receivedAt >= t - messageMaxAge
292− where m.receivedAt > cursor
325+ where m.receivedAt > clampedCursor
326+ yield m
327+ let limitedMsgs = fold (\acc m ->
328+ if count acc >= maxPollEvents then acc
329+ else union acc [m]) [] matchingMsgs
330+ let evts = do
331+ m <- limitedMsgs
293− yield {id: m.messageId, eventType: "message",
332+ yield {id: m.messageId, eventType: "message",
294− payload: toJson {toKey: m.toKey, fromKey: m.fromKey,
333+ payload: toJson {toKey: m.toKey, fromKey: m.fromKey,
295− encryptedBlob: m.encryptedBlob,
334+ encryptedBlob: m.encryptedBlob,
296− timestamp: m.timestamp}}
335+ timestamp: m.timestamp}}
336+ let lastReceivedAt = fold (\acc m -> if m.receivedAt > acc then m.receivedAt else acc) clampedCursor limitedMsgs
297 let authorized = case session of
337 let authorized = case session of
298 Nothing {} -> False {}
338 Nothing {} -> False {}
299 _ -> True {}
339 _ -> True {}
300 let newCursor = case session of
340 let newCursor = case session of
301 Nothing {} -> -1
341 Nothing {} -> -1
342+ _ -> if count limitedMsgs >= maxPollEvents
343+ then lastReceivedAt
302− _ -> if seqCounter > cursor then seqCounter else cursor
344+ else if seqCounter > clampedCursor then seqCounter else clampedCursor
303 yield {events: evts, cursor: newCursor, authorized: authorized, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
345 yield {events: evts, cursor: newCursor, authorized: authorized, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
304− broadcastOfflineGossip t result.peerServers result.expiredLocal
305 respond {events: result.events, cursor: result.cursor, authorized: result.authorized}
346 respond {events: result.events, cursor: result.cursor, authorized: result.authorized}
347+ broadcastOfflineGossip t result.peerServers result.expiredLocal
306
348
307 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
349 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
308 t <- now
350 t <- now
@@ -353,18 +395,21 @@ serve = \req -> case req of
353 else if localOnline then "delivered"
395 else if localOnline then "delivered"
354 else if count remoteServers > 0 then "federated"
396 else if count remoteServers > 0 then "federated"
355 else "queued"
397 else "queued"
398+ forwards <- *forwards
399+ set *forwards = if shouldInsert
400+ then union forwards (do s <- remoteServers; yield {messageId: msgId, toServer: s, createdAt: t})
401+ else forwards
356 yield {status: status, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
402 yield {status: status, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
357− finalStatus <- if result.status == "federated" && not (federationSecret == "")
403+ let finalStatus = if result.status == "federated" && federationSecret == "" then "queued" else result.status
358− then do
359− sendToEach result.remoteServers (\server ->
360− fetch (federationScheme ++ server) (ForwardMessage {
361− toKey: to, fromKey: result.fromKey, encryptedBlob: encryptedBlob,
362− timestamp: clampedTs, originServer: serverHost, messageId: msgId,
363− authorization: "Bearer " ++ federationSecret}))
364− yield "federated"
365− else yield (if result.status == "federated" then "queued" else result.status)
366− broadcastOfflineGossip t result.peerServers result.expiredLocal
367 respond {status: finalStatus, messageId: (if result.status == "unauthorized" then "" else if result.status == "invalid" then "" else msgId)}
404 respond {status: finalStatus, messageId: (if result.status == "unauthorized" then "" else if result.status == "invalid" then "" else msgId)}
405+ if result.status == "federated" && not (federationSecret == "")
406+ then sendToEach result.remoteServers (\server ->
407+ fetch (federationScheme ++ server) (ForwardMessage {
408+ toKey: to, fromKey: result.fromKey, encryptedBlob: encryptedBlob,
409+ timestamp: clampedTs, originServer: serverHost, messageId: msgId,
410+ authorization: "Bearer " ++ federationSecret}))
411+ else yield {}
412+ broadcastOfflineGossip t result.peerServers result.expiredLocal
368
413
369 AckMessages {messageIds, authorization, respond} -> do
414 AckMessages {messageIds, authorization, respond} -> do
370 t <- now
415 t <- now
@@ -403,6 +448,7 @@ serve = \req -> case req of
403 let originServers = fold (\acc m ->
448 let originServers = fold (\acc m ->
404 if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
449 if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
405 else union acc [m.originServer]) [] fedMessages
450 else union acc [m.originServer]) [] fedMessages
451+ respond {ok: result.ok}
406 if result.ok && count originServers > 0 && not (federationSecret == "")
452 if result.ok && count originServers > 0 && not (federationSecret == "")
407 then sendToEach originServers (\server ->
453 then sendToEach originServers (\server ->
408 fetch (federationScheme ++ server) (FederationAck {
454 fetch (federationScheme ++ server) (FederationAck {
@@ -411,7 +457,6 @@ serve = \req -> case req of
411 originServer: server, fromServer: serverHost}))
457 originServer: server, fromServer: serverHost}))
412 else yield {}
458 else yield {}
413 broadcastOfflineGossip t result.peerServers result.expiredLocal
459 broadcastOfflineGossip t result.peerServers result.expiredLocal
414− respond {ok: result.ok}
415
460
416 LookupPresence {pubkey, authorization, respond} -> do
461 LookupPresence {pubkey, authorization, respond} -> do
417 t <- now
462 t <- now
@@ -440,8 +485,8 @@ serve = \req -> case req of
440 Just {value: p} -> p.server
485 Just {value: p} -> p.server
441 Nothing {} -> (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
486 Nothing {} -> (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
442 yield {foundServer: foundServer, authorized: authorized, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
487 yield {foundServer: foundServer, authorized: authorized, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
443− broadcastOfflineGossip t result.peerServers result.expiredLocal
444 respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized}
488 respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized}
489+ broadcastOfflineGossip t result.peerServers result.expiredLocal
445
490
446 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
491 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
447 t <- now
492 t <- now
@@ -457,6 +502,7 @@ serve = \req -> case req of
457 where not (tooLong e.pubkey maxPubkeyLen)
502 where not (tooLong e.pubkey maxPubkeyLen)
458 where not (tooLong e.server maxPubkeyLen)
503 where not (tooLong e.server maxPubkeyLen)
459 where isValidHex e.pubkey
504 where isValidHex e.pubkey
505+ where cappedHops < 2 || e.server == fromServer
460 where not (e.server == serverHost)
506 where not (e.server == serverHost)
461 where e.timestamp <= t + clockSkewTolerance
507 where e.timestamp <= t + clockSkewTolerance
462 where e.timestamp + (clampTtl e.ttl) * 1000 > t
508 where e.timestamp + (clampTtl e.ttl) * 1000 > t
@@ -470,6 +516,7 @@ serve = \req -> case req of
470 where not (tooLong e.pubkey maxPubkeyLen)
516 where not (tooLong e.pubkey maxPubkeyLen)
471 where not (tooLong e.server maxPubkeyLen)
517 where not (tooLong e.server maxPubkeyLen)
472 where isValidHex e.pubkey
518 where isValidHex e.pubkey
519+ where cappedHops < 2 || e.server == fromServer
473 where not (e.server == serverHost)
520 where not (e.server == serverHost)
474 where e.timestamp <= t + clockSkewTolerance
521 where e.timestamp <= t + clockSkewTolerance
475 where t - e.timestamp < (clampTtl e.ttl) * 1000
522 where t - e.timestamp < (clampTtl e.ttl) * 1000
@@ -560,18 +607,67 @@ serve = \req -> case req of
560 where o.timestamp >= p.timestamp
607 where o.timestamp >= p.timestamp
561 yield o) > 0
608 yield o) > 0
562 yield p)
609 yield p)
610+ let withinCapacity = count afterOffline <= maxPresenceEntries
611+ let updatesOnlyMerged = if withinCapacity then afterOffline
612+ else fold (\acc e ->
613+ if count (do
614+ p <- acc
615+ where p.pubkey == e.pubkey
616+ where p.server == e.server
617+ where p.expiresAt >= e.expiresAt
618+ yield p) > 0
619+ then acc
620+ else if count (do p <- presence; where p.pubkey == e.pubkey; where p.server == e.server; yield p) > 0
621+ then union
622+ (do
623+ p <- acc
624+ where not (p.pubkey == e.pubkey && p.server == e.server)
625+ yield p)
626+ [e]
627+ else acc) presence deduped
628+ let updatesOnlyAfterOffline = if withinCapacity then afterOffline
629+ else diff updatesOnlyMerged (do
630+ p <- updatesOnlyMerged
631+ k <- offlineOnlyKeys
632+ where p.pubkey == k.pubkey
633+ where p.server == k.server
634+ where count (do
635+ o <- offlineEntries
636+ where o.pubkey == k.pubkey
637+ where o.server == k.server
638+ where o.timestamp >= p.timestamp
639+ yield o) > 0
640+ yield p)
641+ let appliedOnlineUpdates = if withinCapacity then appliedOnline
642+ else do
643+ e <- deduped
644+ where count (do p <- presence; where p.pubkey == e.pubkey; where p.server == e.server; yield p) > 0
645+ where count (do
646+ p <- presence
647+ where p.pubkey == e.pubkey
648+ where p.server == e.server
649+ where p.expiresAt >= e.expiresAt
650+ yield p) == 0
651+ yield {pubkey: e.pubkey, server: e.server}
563− set *presence = if validRequest
652+ let finalPresence = if validRequest
564− then afterOffline
653+ then if withinCapacity then afterOffline else updatesOnlyAfterOffline
565 else presence
654 else presence
655+ set *presence = finalPresence
656+ let gossipPeerServers = fold (\acc p ->
657+ if p.server == serverHost then acc
658+ else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
659+ else union acc [p.server]) [] finalPresence
566− yield {appliedKeys: (union appliedOnline appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
660+ yield {appliedKeys: (if withinCapacity then union appliedOnline appliedOffline else union appliedOnlineUpdates appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers}
567 let validEvents = do
661 let validEvents = do
568 e <- events
662 e <- events
569 where validRequest
663 where validRequest
664+ where e.eventType == "online" || e.eventType == "offline"
570 where not (e.server == "")
665 where not (e.server == "")
571 where not (e.pubkey == "")
666 where not (e.pubkey == "")
572 where not (tooLong e.pubkey maxPubkeyLen)
667 where not (tooLong e.pubkey maxPubkeyLen)
573 where not (tooLong e.server maxPubkeyLen)
668 where not (tooLong e.server maxPubkeyLen)
574 where isValidHex e.pubkey
669 where isValidHex e.pubkey
670+ where cappedHops < 2 || e.server == fromServer
575 where not (e.server == serverHost)
671 where not (e.server == serverHost)
576 where e.timestamp <= t + clockSkewTolerance
672 where e.timestamp <= t + clockSkewTolerance
577 where e.timestamp + (clampTtl e.ttl) * 1000 > t
673 where e.timestamp + (clampTtl e.ttl) * 1000 > t
@@ -586,19 +682,14 @@ serve = \req -> case req of
586 e <- validEvents
682 e <- validEvents
587 where count (do a <- gossipResult.appliedKeys; where a.pubkey == e.pubkey; where a.server == e.server; yield a) > 0
683 where count (do a <- gossipResult.appliedKeys; where a.pubkey == e.pubkey; where a.server == e.server; yield a) > 0
588 yield e
684 yield e
685+ respond {ok: validRequest}
589 if validRequest && cappedHops > 1 && not (federationSecret == "") && count effectiveEvents > 0
686 if validRequest && cappedHops > 1 && not (federationSecret == "") && count effectiveEvents > 0
590 then do
687 then do
591− peerServers <- atomic do
592− presence <- *presence
593− yield (fold (\acc p ->
594− if p.server == serverHost then acc
595− else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
596− else union acc [p.server]) [] presence)
597 let senderServers = fold (\acc e ->
688 let senderServers = fold (\acc e ->
598 if count (do a <- acc; where a == e.server; yield a) > 0 then acc
689 if count (do a <- acc; where a == e.server; yield a) > 0 then acc
599 else union acc [e.server]) (if fromServer == "" then [] else [fromServer]) effectiveEvents
690 else union acc [e.server]) (if fromServer == "" then [] else [fromServer]) effectiveEvents
600 let targets = do
691 let targets = do
601− s <- peerServers
692+ s <- gossipResult.gossipPeerServers
602 where count (do ss <- senderServers; where ss == s; yield ss) == 0
693 where count (do ss <- senderServers; where ss == s; yield ss) == 0
603 yield s
694 yield s
604 sendToEach targets (\server ->
695 sendToEach targets (\server ->
@@ -607,7 +698,6 @@ serve = \req -> case req of
607 events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHost}))
698 events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHost}))
608 else yield {}
699 else yield {}
609 broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal
700 broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal
610− respond {ok: validRequest}
611
701
612 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
702 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
613 t <- now
703 t <- now
@@ -615,10 +705,16 @@ serve = \req -> case req of
615 let validOrigin = not (originServer == "") && not (originServer == serverHost) && not (tooLong originServer maxPubkeyLen)
705 let validOrigin = not (originServer == "") && not (originServer == serverHost) && not (tooLong originServer maxPubkeyLen)
616 let validFields = not (toKey == "") && not (fromKey == "") && not (encryptedBlob == "") && not (messageId == "") && not (tooLong toKey maxPubkeyLen) && not (tooLong fromKey maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen) && not (tooLong messageId maxPubkeyLen) && isValidHex toKey && isValidHex fromKey && isValidHex encryptedBlob && isValidHex messageId
706 let validFields = not (toKey == "") && not (fromKey == "") && not (encryptedBlob == "") && not (messageId == "") && not (tooLong toKey maxPubkeyLen) && not (tooLong fromKey maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen) && not (tooLong messageId maxPubkeyLen) && isValidHex toKey && isValidHex fromKey && isValidHex encryptedBlob && isValidHex messageId
617 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
707 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
618− pruneResult <- atomic do
708+ result <- atomic do
619 pr <- pruneExpired t
709 pr <- pruneExpired t
710+ presence <- *presence
620 messages <- *messages
711 messages <- *messages
621 seqCounter <- *seqCounter
712 seqCounter <- *seqCounter
713+ let knownPeer = count (do
714+ p <- presence
715+ where p.server == originServer
716+ where p.expiresAt > t
717+ yield p) > 0
622 let seqTs = if t > seqCounter then t else seqCounter + 1
718 let seqTs = if t > seqCounter then t else seqCounter + 1
623 let exists = count (do
719 let exists = count (do
624 m <- messages
720 m <- messages
@@ -626,16 +722,16 @@ serve = \req -> case req of
626 where m.toKey == toKey
722 where m.toKey == toKey
627 where m.receivedAt >= t - messageMaxAge
723 where m.receivedAt >= t - messageMaxAge
628 yield m) > 0
724 yield m) > 0
629− let shouldInsert = authorized && validOrigin && validFields && not exists
725+ let shouldInsert = authorized && validOrigin && validFields && knownPeer && not exists
630 set *seqCounter = if shouldInsert then seqTs else seqCounter
726 set *seqCounter = if shouldInsert then seqTs else seqCounter
631 set *messages = if shouldInsert
727 set *messages = if shouldInsert
632 then union messages
728 then union messages
633 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
729 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
634 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
730 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
635 else messages
731 else messages
636− yield pr
732+ yield {peerServers: pr.peerServers, expiredLocal: pr.expiredLocal, knownPeer: knownPeer}
637− broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
638− respond {ok: authorized && validOrigin && validFields}
733+ respond {ok: authorized && validOrigin && validFields && result.knownPeer}
734+ broadcastOfflineGossip t result.peerServers result.expiredLocal
639
735
640 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
736 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
641 t <- now
737 t <- now
@@ -647,17 +743,29 @@ serve = \req -> case req of
647 else if not (isValidHex mid) then acc
743 else if not (isValidHex mid) then acc
648 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
744 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
649 else union acc [mid]) [] messageIds
745 else union acc [mid]) [] messageIds
650− let validAck = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen)
746+ let validAckFields = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen)
651 pruneResult <- atomic do
747 pruneResult <- atomic do
652 pr <- pruneExpired t
748 pr <- pruneExpired t
653 messages <- *messages
749 messages <- *messages
654 presence <- *presence
750 presence <- *presence
751+ forwards <- *forwards
752+ let knownPeer = count (do
753+ p <- presence
754+ where p.server == fromServer
755+ where p.expiresAt > t
756+ yield p) > 0
757+ let validAck = validAckFields && knownPeer
655 set *messages = if validAck
758 set *messages = if validAck
656 then diff messages (do
759 then diff messages (do
657 m <- messages
760 m <- messages
658 mid <- dedupedIds
761 mid <- dedupedIds
659 where m.messageId == mid
762 where m.messageId == mid
660 where m.originServer == ""
763 where m.originServer == ""
764+ where count (do
765+ f <- forwards
766+ where f.messageId == mid
767+ where f.toServer == fromServer
768+ yield f) > 0
661 where count (do
769 where count (do
662 p <- presence
770 p <- presence
663 where p.pubkey == m.toKey
771 where p.pubkey == m.toKey
@@ -666,9 +774,9 @@ serve = \req -> case req of
666 yield p) == 0
774 yield p) == 0
667 yield m)
775 yield m)
668 else messages
776 else messages
669− yield pr
777+ yield {peerServers: pr.peerServers, expiredLocal: pr.expiredLocal, validAck: validAck}
778+ respond {ok: pruneResult.validAck}
670 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
779 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
671− respond {ok: validAck}
672
780
673-- ============================================
781-- ============================================
674-- Entry Point
782-- Entry Point