@@ -16,6 +16,7 @@ maxBlobLen = 65536
1616maxSignatureLen = 256
1717maxMessageIds = 100
1818maxGossipEvents = 100
19+clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
1920
2021-- ============================================
2122-- Types
@@ -94,6 +95,8 @@ genToken = do
9495
9596tooLong = \s maxLen -> not (drop maxLen s == "")
9697
98+isValidHex = \s -> not (s == "") && bytesToHex (bytesFromHex s) == s
99+
97100lookupSession = \sessions token t ->
98101 single (do
99102 s <- sessions
@@ -148,6 +151,10 @@ pruneExpired = \t -> do
148151 where s.expiresAt > t
149152 yield s
150153 presenceBefore <- *presence
154+ let peerServersBefore = fold (\acc p ->
155+ if p.server == serverHost then acc
156+ else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
157+ else union acc [p.server]) [] presenceBefore
151158 let presenceAfter = do
152159 p <- presenceBefore
153160 where p.expiresAt > t
@@ -158,7 +165,7 @@ pruneExpired = \t -> do
158165 m <- messages
159166 where m.timestamp >= t - messageMaxAge
160167 yield m
161− yield (do
168+ yield {expiredLocal: (do
162169 p <- presenceBefore
163170 where p.expiresAt <= t
164171 where p.server == serverHost
@@ -167,20 +174,14 @@ pruneExpired = \t -> do
167174 where q.pubkey == p.pubkey
168175 where q.server == serverHost
169176 yield q) == 0
170− yield p)
177+ yield p), peerServers: peerServersBefore}
171178
172−broadcastOfflineGossip = \t expiredLocal -> do
179+broadcastOfflineGossip = \t peerServers expiredLocal -> do
173180 let expiredPubkeys = fold (\acc p ->
174181 if count (do a <- acc; where a == p.pubkey; yield a) > 0 then acc
175182 else union acc [p.pubkey]) [] expiredLocal
176− if not (federationSecret == "") && count expiredPubkeys > 0
183+ if not (federationSecret == "") && count expiredPubkeys > 0 && count peerServers > 0
177184 then do
178− peerServers <- atomic do
179− presence <- *presence
180− yield (fold (\acc p ->
181− if p.server == serverHost then acc
182− else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
183− else union acc [p.server]) [] presence)
184185 let gossipEvents = do
185186 pk <- expiredPubkeys
186187 yield {eventType: "offline", pubkey: pk, server: serverHost, timestamp: t, ttl: 3600}
@@ -202,21 +203,21 @@ serve = \req -> case req of
202203 else do
203204 challenge <- genToken
204205 t <- now
205− expiredLocal <- atomic do
206− el <- pruneExpired t
206+ pruneResult <- atomic do
207+ pr <- pruneExpired t
207208 challenges <- *challenges
208209 set *challenges = union challenges
209210 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
210− yield el
211+ yield pr
211212 respond {challenge: challenge}
212− broadcastOfflineGossip t expiredLocal
213+ broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
213214
214215 VerifyAuth {pubkey, challenge, signature, respond} -> do
215216 t <- now
216217 token <- genToken
217218 let expiry = t + 3600000
218219 result <- atomic do
219− expiredLocal <- pruneExpired t
220+ pr <- pruneExpired t
220221 challenges <- *challenges
221222 sessions <- *sessions
222223 presence <- *presence
@@ -229,6 +230,8 @@ serve = \req -> case req of
229230 let sigValid = case matched of
230231 Nothing {} -> False {}
231232 _ -> if tooLong signature maxSignatureLen
233+ then False {}
234+ else if not (isValidHex pubkey) || not (isValidHex signature)
232235 then False {}
233236 else verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
234237 set *challenges = if sigValid
@@ -268,7 +271,7 @@ serve = \req -> case req of
268271 else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
269272 else union acc [p.server]) [] updatedPresence
270273 else []
271− yield {sigValid: sigValid, peerServers: peerServers, expiredLocal: expiredLocal}
274+ yield {sigValid: sigValid, peerServers: peerServers, expiredLocal: pr.expiredLocal, offlinePeers: pr.peerServers}
272275 if result.sigValid
273276 then respond {token: token, expiresAt: expiry}
274277 else respond {token: "", expiresAt: 0}
@@ -278,13 +281,13 @@ serve = \req -> case req of
278281 {authorization: "Bearer " ++ federationSecret}
279282 {events: [{eventType: "online", pubkey: pubkey, server: serverHost, timestamp: t, ttl: 3600}], hopsRemaining: 2, fromServer: serverHost})
280283 else yield {}
281− broadcastOfflineGossip t result.expiredLocal
284+ broadcastOfflineGossip t result.offlinePeers result.expiredLocal
282285
283286 Poll {cursor, authorization, respond} -> do
284287 t <- now
285288 let token = extractToken authorization
286289 result <- atomic do
287− expiredLocal <- pruneExpired t
290+ pr <- pruneExpired t
288291 sessions <- *sessions
289292 messages <- *messages
290293 seqCounter <- *seqCounter
@@ -301,11 +304,11 @@ serve = \req -> case req of
301304 encryptedBlob: m.encryptedBlob,
302305 timestamp: m.timestamp}}
303306 let newCursor = case session of
304− Nothing {} -> 0
307+ Nothing {} -> -1
305308 _ -> if seqCounter > cursor then seqCounter else cursor
306− yield {events: evts, cursor: newCursor, expiredLocal: expiredLocal}
309+ yield {events: evts, cursor: newCursor, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
307310 respond {events: result.events, cursor: result.cursor}
308− broadcastOfflineGossip t result.expiredLocal
311+ broadcastOfflineGossip t result.peerServers result.expiredLocal
309312
310313 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
311314 t <- now
@@ -313,7 +316,7 @@ serve = \req -> case req of
313316 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
314317 let validInput = not (to == "") && not (encryptedBlob == "") && not (tooLong to maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen)
315318 result <- atomic do
316− expiredLocal <- pruneExpired t
319+ pr <- pruneExpired t
317320 sessions <- *sessions
318321 messages <- *messages
319322 presence <- *presence
@@ -356,7 +359,7 @@ serve = \req -> case req of
356359 else if localOnline then "delivered"
357360 else if count remoteServers > 0 then "federated"
358361 else "queued"
359− yield {status: status, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: expiredLocal}
362+ yield {status: status, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
360363 finalStatus <- if result.status == "federated" && not (federationSecret == "")
361364 then do
362365 anyOk <- forwardToAll result.remoteServers "/federation/forward"
@@ -366,7 +369,7 @@ serve = \req -> case req of
366369 yield (if anyOk then "federated" else "queued")
367370 else yield result.status
368371 respond {status: finalStatus, messageId: (if result.status == "unauthorized" then "" else if result.status == "invalid" then "" else msgId)}
369− broadcastOfflineGossip t result.expiredLocal
372+ broadcastOfflineGossip t result.peerServers result.expiredLocal
370373
371374 AckMessages {messageIds, authorization, respond} -> do
372375 t <- now
@@ -376,7 +379,7 @@ serve = \req -> case req of
376379 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
377380 else union acc [mid]) [] messageIds
378381 result <- atomic do
379− expiredLocal <- pruneExpired t
382+ pr <- pruneExpired t
380383 sessions <- *sessions
381384 messages <- *messages
382385 let session = lookupSession sessions (extractToken authorization) t
@@ -394,7 +397,7 @@ serve = \req -> case req of
394397 let ok = case session of
395398 Nothing {} -> False {}
396399 _ -> True {}
397− yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: expiredLocal}
400+ yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
398401 let fedMessages = do
399402 m <- result.ackedMessages
400403 where not (m.originServer == "")
@@ -411,12 +414,12 @@ serve = \req -> case req of
411414 originServer: server, fromServer: serverHost})
412415 else yield {}
413416 respond {ok: result.ok}
414− broadcastOfflineGossip t result.expiredLocal
417+ broadcastOfflineGossip t result.peerServers result.expiredLocal
415418
416419 LookupPresence {pubkey, authorization, respond} -> do
417420 t <- now
418421 result <- atomic do
419− expiredLocal <- pruneExpired t
422+ pr <- pruneExpired t
420423 sessions <- *sessions
421424 presence <- *presence
422425 let session = lookupSession sessions (extractToken authorization) t
@@ -439,15 +442,15 @@ serve = \req -> case req of
439442 yield p) of
440443 Just {value: p} -> p.server
441444 Nothing {} -> fold (\_ p -> p.server) "" valid
442− yield {foundServer: foundServer, authorized: authorized, expiredLocal: expiredLocal}
445+ yield {foundServer: foundServer, authorized: authorized, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
443446 respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized}
444− broadcastOfflineGossip t result.expiredLocal
447+ broadcastOfflineGossip t result.peerServers result.expiredLocal
445448
446449 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
447450 t <- now
448451 let authorized = verifyFederationAuth authorization
449452 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
450− let validRequest = authorized && cappedHops >= 1 && not (fromServer == "") && count events <= maxGossipEvents
453+ let validRequest = authorized && cappedHops >= 1 && not (fromServer == "") && not (tooLong fromServer maxPubkeyLen) && count events <= maxGossipEvents
451454 let onlineEntries = do
452455 e <- events
453456 where validRequest
@@ -457,7 +460,7 @@ serve = \req -> case req of
457460 where not (tooLong e.pubkey maxPubkeyLen)
458461 where not (tooLong e.server maxPubkeyLen)
459462 where not (e.server == serverHost)
460− where e.timestamp <= t
463+ where e.timestamp <= t + clockSkewTolerance
461464 where e.timestamp + (clampTtl e.ttl) * 1000 > t
462465 yield {pubkey: e.pubkey, server: e.server, expiresAt: e.timestamp + (clampTtl e.ttl) * 1000, timestamp: e.timestamp}
463466 let offlineEntries = do
@@ -469,7 +472,7 @@ serve = \req -> case req of
469472 where not (tooLong e.pubkey maxPubkeyLen)
470473 where not (tooLong e.server maxPubkeyLen)
471474 where not (e.server == serverHost)
472− where e.timestamp <= t
475+ where e.timestamp <= t + clockSkewTolerance
473476 where t - e.timestamp < (clampTtl e.ttl) * 1000
474477 yield {pubkey: e.pubkey, server: e.server, timestamp: e.timestamp}
475478 let filteredOnline = do
@@ -503,7 +506,7 @@ serve = \req -> case req of
503506 yield a)
504507 [e]) [] filteredOnline
505508 gossipResult <- atomic do
506− expiredLocal <- pruneExpired t
509+ pr <- pruneExpired t
507510 presence <- *presence
508511 let appliedOnline = do
509512 e <- deduped
@@ -561,7 +564,7 @@ serve = \req -> case req of
561564 set *presence = if validRequest
562565 then afterOffline
563566 else presence
564− yield {appliedKeys: (union appliedOnline appliedOffline), expiredLocal: expiredLocal}
567+ yield {appliedKeys: (union appliedOnline appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
565568 let validEvents = do
566569 e <- events
567570 where validRequest
@@ -570,7 +573,7 @@ serve = \req -> case req of
570573 where not (tooLong e.pubkey maxPubkeyLen)
571574 where not (tooLong e.server maxPubkeyLen)
572575 where not (e.server == serverHost)
573− where e.timestamp <= t
576+ where e.timestamp <= t + clockSkewTolerance
574577 where e.timestamp + (clampTtl e.ttl) * 1000 > t
575578 where not (e.eventType == "online" && count (do
576579 o <- offlineEntries
@@ -593,7 +596,7 @@ serve = \req -> case req of
593596 else union acc [p.server]) [] presence)
594597 let senderServers = fold (\acc e ->
595598 if count (do a <- acc; where a == e.server; yield a) > 0 then acc
596− else union acc [e.server]) (if fromServer == "" then [] else [fromServer]) validEvents
599+ else union acc [e.server]) (if fromServer == "" then [] else [fromServer]) effectiveEvents
597600 let targets = do
598601 s <- peerServers
599602 where count (do ss <- senderServers; where ss == s; yield ss) == 0
@@ -604,7 +607,7 @@ serve = \req -> case req of
604607 {events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHost})
605608 else yield {}
606609 respond {ok: validRequest}
607− broadcastOfflineGossip t gossipResult.expiredLocal
610+ broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal
608611
609612 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
610613 t <- now
@@ -612,8 +615,8 @@ serve = \req -> case req of
612615 let validOrigin = not (originServer == "") && not (originServer == serverHost)
613616 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)
614617 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
615− expiredLocal <- atomic do
616− el <- pruneExpired t
618+ pruneResult <- atomic do
619+ pr <- pruneExpired t
617620 messages <- *messages
618621 seqCounter <- *seqCounter
619622 let seqTs = if t > seqCounter then t else seqCounter + 1
@@ -629,9 +632,9 @@ serve = \req -> case req of
629632 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
630633 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
631634 else messages
632− yield el
635+ yield pr
633636 respond {ok: authorized && validOrigin && validFields}
634− broadcastOfflineGossip t expiredLocal
637+ broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
635638
636639 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
637640 t <- now
@@ -642,8 +645,8 @@ serve = \req -> case req of
642645 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
643646 else union acc [mid]) [] messageIds
644647 let validAck = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && count messageIds <= maxMessageIds
645− expiredLocal <- atomic do
646− el <- pruneExpired t
648+ pruneResult <- atomic do
649+ pr <- pruneExpired t
647650 messages <- *messages
648651 presence <- *presence
649652 set *messages = if validAck
@@ -651,6 +654,7 @@ serve = \req -> case req of
651654 m <- messages
652655 mid <- dedupedIds
653656 where m.messageId == mid
657+ where m.originServer == ""
654658 where count (do
655659 p <- presence
656660 where p.pubkey == m.toKey
@@ -659,9 +663,9 @@ serve = \req -> case req of
659663 yield p) == 0
660664 yield m)
661665 else messages
662− yield el
666+ yield pr
663667 respond {ok: validAck}
664− broadcastOfflineGossip t expiredLocal
668+ broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
665669
666670-- ============================================
667671-- Entry Point