@@ -16,6 +16,7 @@ maxBlobLen = 65536
16maxSignatureLen = 256
16maxSignatureLen = 256
17maxMessageIds = 100
17maxMessageIds = 100
18maxGossipEvents = 100
18maxGossipEvents = 100
19+clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
20-- ============================================
21-- ============================================
@@ -94,6 +95,8 @@ genToken = do
95tooLong = \s maxLen -> not (drop maxLen s == "")
96tooLong = \s maxLen -> not (drop maxLen s == "")
98+isValidHex = \s -> not (s == "") && bytesToHex (bytesFromHex s) == s
97lookupSession = \sessions token t ->
100lookupSession = \sessions token t ->
98 single (do
101 single (do
99 s <- sessions
102 s <- sessions
@@ -148,6 +151,10 @@ pruneExpired = \t -> do
148 where s.expiresAt > t
151 where s.expiresAt > t
150 presenceBefore <- *presence
153 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
151 let presenceAfter = do
158 let presenceAfter = do
152 p <- presenceBefore
159 p <- presenceBefore
153 where p.expiresAt > t
160 where p.expiresAt > t
@@ -158,7 +165,7 @@ pruneExpired = \t -> do
158 m <- messages
165 m <- messages
159 where m.timestamp >= t - messageMaxAge
166 where m.timestamp >= t - messageMaxAge
161− yield (do
168+ yield {expiredLocal: (do
162 p <- presenceBefore
169 p <- presenceBefore
163 where p.expiresAt <= t
170 where p.expiresAt <= t
164 where p.server == serverHost
171 where p.server == serverHost
@@ -167,20 +174,14 @@ pruneExpired = \t -> do
167 where q.pubkey == p.pubkey
174 where q.pubkey == p.pubkey
168 where q.server == serverHost
175 where q.server == serverHost
169 yield q) == 0
176 yield q) == 0
170− yield p)
177+ yield p), peerServers: peerServersBefore}
172−broadcastOfflineGossip = \t expiredLocal -> do
179+broadcastOfflineGossip = \t peerServers expiredLocal -> do
173 let expiredPubkeys = fold (\acc p ->
180 let expiredPubkeys = fold (\acc p ->
174 if count (do a <- acc; where a == p.pubkey; yield a) > 0 then acc
181 if count (do a <- acc; where a == p.pubkey; yield a) > 0 then acc
175 else union acc [p.pubkey]) [] expiredLocal
182 else union acc [p.pubkey]) [] expiredLocal
176− if not (federationSecret == "") && count expiredPubkeys > 0
183+ if not (federationSecret == "") && count expiredPubkeys > 0 && count peerServers > 0
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)
184 let gossipEvents = do
185 let gossipEvents = do
185 pk <- expiredPubkeys
186 pk <- expiredPubkeys
186 yield {eventType: "offline", pubkey: pk, server: serverHost, timestamp: t, ttl: 3600}
187 yield {eventType: "offline", pubkey: pk, server: serverHost, timestamp: t, ttl: 3600}
@@ -202,21 +203,21 @@ serve = \req -> case req of
203 challenge <- genToken
204 challenge <- genToken
205− expiredLocal <- atomic do
206+ pruneResult <- atomic do
206− el <- pruneExpired t
207+ pr <- pruneExpired t
207 challenges <- *challenges
208 challenges <- *challenges
208 set *challenges = union challenges
209 set *challenges = union challenges
209 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
210 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
210− yield el
211+ yield pr
211 respond {challenge: challenge}
212 respond {challenge: challenge}
212− broadcastOfflineGossip t expiredLocal
213+ broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
214 VerifyAuth {pubkey, challenge, signature, respond} -> do
215 VerifyAuth {pubkey, challenge, signature, respond} -> do
216 token <- genToken
217 token <- genToken
217 let expiry = t + 3600000
218 let expiry = t + 3600000
218 result <- atomic do
219 result <- atomic do
219− expiredLocal <- pruneExpired t
220+ pr <- pruneExpired t
220 challenges <- *challenges
221 challenges <- *challenges
221 sessions <- *sessions
222 sessions <- *sessions
222 presence <- *presence
223 presence <- *presence
@@ -229,6 +230,8 @@ serve = \req -> case req of
229 let sigValid = case matched of
230 let sigValid = case matched of
230 Nothing {} -> False {}
231 Nothing {} -> False {}
231 _ -> if tooLong signature maxSignatureLen
232 _ -> if tooLong signature maxSignatureLen
234+ else if not (isValidHex pubkey) || not (isValidHex signature)
232 then False {}
235 then False {}
233 else verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
236 else verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
234 set *challenges = if sigValid
237 set *challenges = if sigValid
@@ -268,7 +271,7 @@ serve = \req -> case req of
268 else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
271 else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
269 else union acc [p.server]) [] updatedPresence
272 else union acc [p.server]) [] updatedPresence
271− yield {sigValid: sigValid, peerServers: peerServers, expiredLocal: expiredLocal}
274+ yield {sigValid: sigValid, peerServers: peerServers, expiredLocal: pr.expiredLocal, offlinePeers: pr.peerServers}
272 if result.sigValid
275 if result.sigValid
273 then respond {token: token, expiresAt: expiry}
276 then respond {token: token, expiresAt: expiry}
274 else respond {token: "", expiresAt: 0}
277 else respond {token: "", expiresAt: 0}
@@ -278,13 +281,13 @@ serve = \req -> case req of
278 {authorization: "Bearer " ++ federationSecret}
281 {authorization: "Bearer " ++ federationSecret}
279 {events: [{eventType: "online", pubkey: pubkey, server: serverHost, timestamp: t, ttl: 3600}], hopsRemaining: 2, fromServer: serverHost})
282 {events: [{eventType: "online", pubkey: pubkey, server: serverHost, timestamp: t, ttl: 3600}], hopsRemaining: 2, fromServer: serverHost})
280 else yield {}
283 else yield {}
281− broadcastOfflineGossip t result.expiredLocal
284+ broadcastOfflineGossip t result.offlinePeers result.expiredLocal
283 Poll {cursor, authorization, respond} -> do
286 Poll {cursor, authorization, respond} -> do
285 let token = extractToken authorization
288 let token = extractToken authorization
286 result <- atomic do
289 result <- atomic do
287− expiredLocal <- pruneExpired t
290+ pr <- pruneExpired t
288 sessions <- *sessions
291 sessions <- *sessions
289 messages <- *messages
292 messages <- *messages
290 seqCounter <- *seqCounter
293 seqCounter <- *seqCounter
@@ -301,11 +304,11 @@ serve = \req -> case req of
301 encryptedBlob: m.encryptedBlob,
304 encryptedBlob: m.encryptedBlob,
302 timestamp: m.timestamp}}
305 timestamp: m.timestamp}}
303 let newCursor = case session of
306 let newCursor = case session of
304− Nothing {} -> 0
307+ Nothing {} -> -1
305 _ -> if seqCounter > cursor then seqCounter else cursor
308 _ -> 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}
307 respond {events: result.events, cursor: result.cursor}
310 respond {events: result.events, cursor: result.cursor}
308− broadcastOfflineGossip t result.expiredLocal
311+ broadcastOfflineGossip t result.peerServers result.expiredLocal
310 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
313 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
@@ -313,7 +316,7 @@ serve = \req -> case req of
313 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
316 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
314 let validInput = not (to == "") && not (encryptedBlob == "") && not (tooLong to maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen)
317 let validInput = not (to == "") && not (encryptedBlob == "") && not (tooLong to maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen)
315 result <- atomic do
318 result <- atomic do
316− expiredLocal <- pruneExpired t
319+ pr <- pruneExpired t
317 sessions <- *sessions
320 sessions <- *sessions
318 messages <- *messages
321 messages <- *messages
319 presence <- *presence
322 presence <- *presence
@@ -356,7 +359,7 @@ serve = \req -> case req of
356 else if localOnline then "delivered"
359 else if localOnline then "delivered"
357 else if count remoteServers > 0 then "federated"
360 else if count remoteServers > 0 then "federated"
358 else "queued"
361 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}
360 finalStatus <- if result.status == "federated" && not (federationSecret == "")
363 finalStatus <- if result.status == "federated" && not (federationSecret == "")
362 anyOk <- forwardToAll result.remoteServers "/federation/forward"
365 anyOk <- forwardToAll result.remoteServers "/federation/forward"
@@ -366,7 +369,7 @@ serve = \req -> case req of
366 yield (if anyOk then "federated" else "queued")
369 yield (if anyOk then "federated" else "queued")
367 else yield result.status
370 else yield result.status
368 respond {status: finalStatus, messageId: (if result.status == "unauthorized" then "" else if result.status == "invalid" then "" else msgId)}
371 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
371 AckMessages {messageIds, authorization, respond} -> do
374 AckMessages {messageIds, authorization, respond} -> do
@@ -376,7 +379,7 @@ serve = \req -> case req of
376 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
379 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
377 else union acc [mid]) [] messageIds
380 else union acc [mid]) [] messageIds
378 result <- atomic do
381 result <- atomic do
379− expiredLocal <- pruneExpired t
382+ pr <- pruneExpired t
380 sessions <- *sessions
383 sessions <- *sessions
381 messages <- *messages
384 messages <- *messages
382 let session = lookupSession sessions (extractToken authorization) t
385 let session = lookupSession sessions (extractToken authorization) t
@@ -394,7 +397,7 @@ serve = \req -> case req of
394 let ok = case session of
397 let ok = case session of
395 Nothing {} -> False {}
398 Nothing {} -> False {}
396 _ -> True {}
399 _ -> True {}
397− yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: expiredLocal}
400+ yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
398 let fedMessages = do
401 let fedMessages = do
399 m <- result.ackedMessages
402 m <- result.ackedMessages
400 where not (m.originServer == "")
403 where not (m.originServer == "")
@@ -411,12 +414,12 @@ serve = \req -> case req of
411 originServer: server, fromServer: serverHost})
414 originServer: server, fromServer: serverHost})
412 else yield {}
415 else yield {}
413 respond {ok: result.ok}
416 respond {ok: result.ok}
414− broadcastOfflineGossip t result.expiredLocal
417+ broadcastOfflineGossip t result.peerServers result.expiredLocal
416 LookupPresence {pubkey, authorization, respond} -> do
419 LookupPresence {pubkey, authorization, respond} -> do
418 result <- atomic do
421 result <- atomic do
419− expiredLocal <- pruneExpired t
422+ pr <- pruneExpired t
420 sessions <- *sessions
423 sessions <- *sessions
421 presence <- *presence
424 presence <- *presence
422 let session = lookupSession sessions (extractToken authorization) t
425 let session = lookupSession sessions (extractToken authorization) t
@@ -439,15 +442,15 @@ serve = \req -> case req of
439 yield p) of
442 yield p) of
440 Just {value: p} -> p.server
443 Just {value: p} -> p.server
441 Nothing {} -> fold (\_ p -> p.server) "" valid
444 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}
443 respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized}
446 respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized}
444− broadcastOfflineGossip t result.expiredLocal
447+ broadcastOfflineGossip t result.peerServers result.expiredLocal
446 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
449 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
448 let authorized = verifyFederationAuth authorization
451 let authorized = verifyFederationAuth authorization
449 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
452 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
451 let onlineEntries = do
454 let onlineEntries = do
452 e <- events
455 e <- events
453 where validRequest
456 where validRequest
@@ -457,7 +460,7 @@ serve = \req -> case req of
457 where not (tooLong e.pubkey maxPubkeyLen)
460 where not (tooLong e.pubkey maxPubkeyLen)
458 where not (tooLong e.server maxPubkeyLen)
461 where not (tooLong e.server maxPubkeyLen)
459 where not (e.server == serverHost)
462 where not (e.server == serverHost)
460− where e.timestamp <= t
463+ where e.timestamp <= t + clockSkewTolerance
461 where e.timestamp + (clampTtl e.ttl) * 1000 > t
464 where e.timestamp + (clampTtl e.ttl) * 1000 > t
462 yield {pubkey: e.pubkey, server: e.server, expiresAt: e.timestamp + (clampTtl e.ttl) * 1000, timestamp: e.timestamp}
465 yield {pubkey: e.pubkey, server: e.server, expiresAt: e.timestamp + (clampTtl e.ttl) * 1000, timestamp: e.timestamp}
463 let offlineEntries = do
466 let offlineEntries = do
@@ -469,7 +472,7 @@ serve = \req -> case req of
469 where not (tooLong e.pubkey maxPubkeyLen)
472 where not (tooLong e.pubkey maxPubkeyLen)
470 where not (tooLong e.server maxPubkeyLen)
473 where not (tooLong e.server maxPubkeyLen)
471 where not (e.server == serverHost)
474 where not (e.server == serverHost)
472− where e.timestamp <= t
475+ where e.timestamp <= t + clockSkewTolerance
473 where t - e.timestamp < (clampTtl e.ttl) * 1000
476 where t - e.timestamp < (clampTtl e.ttl) * 1000
474 yield {pubkey: e.pubkey, server: e.server, timestamp: e.timestamp}
477 yield {pubkey: e.pubkey, server: e.server, timestamp: e.timestamp}
475 let filteredOnline = do
478 let filteredOnline = do
@@ -503,7 +506,7 @@ serve = \req -> case req of
504 [e]) [] filteredOnline
507 [e]) [] filteredOnline
505 gossipResult <- atomic do
508 gossipResult <- atomic do
506− expiredLocal <- pruneExpired t
509+ pr <- pruneExpired t
507 presence <- *presence
510 presence <- *presence
508 let appliedOnline = do
511 let appliedOnline = do
509 e <- deduped
512 e <- deduped
@@ -561,7 +564,7 @@ serve = \req -> case req of
561 set *presence = if validRequest
564 set *presence = if validRequest
562 then afterOffline
565 then afterOffline
563 else presence
566 else presence
564− yield {appliedKeys: (union appliedOnline appliedOffline), expiredLocal: expiredLocal}
567+ yield {appliedKeys: (union appliedOnline appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
565 let validEvents = do
568 let validEvents = do
566 e <- events
569 e <- events
567 where validRequest
570 where validRequest
@@ -570,7 +573,7 @@ serve = \req -> case req of
570 where not (tooLong e.pubkey maxPubkeyLen)
573 where not (tooLong e.pubkey maxPubkeyLen)
571 where not (tooLong e.server maxPubkeyLen)
574 where not (tooLong e.server maxPubkeyLen)
572 where not (e.server == serverHost)
575 where not (e.server == serverHost)
573− where e.timestamp <= t
576+ where e.timestamp <= t + clockSkewTolerance
574 where e.timestamp + (clampTtl e.ttl) * 1000 > t
577 where e.timestamp + (clampTtl e.ttl) * 1000 > t
575 where not (e.eventType == "online" && count (do
578 where not (e.eventType == "online" && count (do
576 o <- offlineEntries
579 o <- offlineEntries
@@ -593,7 +596,7 @@ serve = \req -> case req of
593 else union acc [p.server]) [] presence)
596 else union acc [p.server]) [] presence)
594 let senderServers = fold (\acc e ->
597 let senderServers = fold (\acc e ->
595 if count (do a <- acc; where a == e.server; yield a) > 0 then acc
598 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
597 let targets = do
600 let targets = do
598 s <- peerServers
601 s <- peerServers
599 where count (do ss <- senderServers; where ss == s; yield ss) == 0
602 where count (do ss <- senderServers; where ss == s; yield ss) == 0
@@ -604,7 +607,7 @@ serve = \req -> case req of
604 {events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHost})
607 {events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHost})
605 else yield {}
608 else yield {}
606 respond {ok: validRequest}
609 respond {ok: validRequest}
607− broadcastOfflineGossip t gossipResult.expiredLocal
610+ broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal
609 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
612 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
@@ -612,8 +615,8 @@ serve = \req -> case req of
612 let validOrigin = not (originServer == "") && not (originServer == serverHost)
615 let validOrigin = not (originServer == "") && not (originServer == serverHost)
613 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)
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)
614 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
617 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
615− expiredLocal <- atomic do
618+ pruneResult <- atomic do
616− el <- pruneExpired t
619+ pr <- pruneExpired t
617 messages <- *messages
620 messages <- *messages
618 seqCounter <- *seqCounter
621 seqCounter <- *seqCounter
619 let seqTs = if t > seqCounter then t else seqCounter + 1
622 let seqTs = if t > seqCounter then t else seqCounter + 1
@@ -629,9 +632,9 @@ serve = \req -> case req of
629 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
632 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
630 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
633 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
631 else messages
634 else messages
632− yield el
635+ yield pr
633 respond {ok: authorized && validOrigin && validFields}
636 respond {ok: authorized && validOrigin && validFields}
634− broadcastOfflineGossip t expiredLocal
637+ broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
636 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
639 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
@@ -642,8 +645,8 @@ serve = \req -> case req of
642 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
645 else if count (do a <- acc; where a == mid; yield a) > 0 then acc
643 else union acc [mid]) [] messageIds
646 else union acc [mid]) [] messageIds
644 let validAck = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && count messageIds <= maxMessageIds
647 let validAck = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && count messageIds <= maxMessageIds
645− expiredLocal <- atomic do
648+ pruneResult <- atomic do
646− el <- pruneExpired t
649+ pr <- pruneExpired t
647 messages <- *messages
650 messages <- *messages
648 presence <- *presence
651 presence <- *presence
649 set *messages = if validAck
652 set *messages = if validAck
@@ -651,6 +654,7 @@ serve = \req -> case req of
651 m <- messages
654 m <- messages
652 mid <- dedupedIds
655 mid <- dedupedIds
653 where m.messageId == mid
656 where m.messageId == mid
657+ where m.originServer == ""
654 where count (do
658 where count (do
655 p <- presence
659 p <- presence
656 where p.pubkey == m.toKey
660 where p.pubkey == m.toKey
@@ -659,9 +663,9 @@ serve = \req -> case req of
659 yield p) == 0
663 yield p) == 0
661 else messages
665 else messages
662− yield el
666+ yield pr
663 respond {ok: validAck}
667 respond {ok: validAck}
664− broadcastOfflineGossip t expiredLocal
668+ broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
666-- ============================================
670-- ============================================
667-- Entry Point
671-- Entry Point