mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 04:07:07 +00:00
117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
package xmpp
|
|
|
|
import (
|
|
"encoding/xml"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"dev.narayana.im/narayana/telegabber/e2ee"
|
|
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"gosrc.io/xmpp"
|
|
"gosrc.io/xmpp/stanza"
|
|
)
|
|
|
|
// handleGetE2EEPubSubIq answers a PEP GET request for one of the e2ee
|
|
// backend's own device-list/bundle nodes, in whichever of its Variants the
|
|
// request actually names - available whenever the E2EE feature is
|
|
// deploy-enabled at all (config's :xmpp: :e2ee: :enabled), regardless of
|
|
// whether this specific chat has actually gone OMEMO-active yet. This is
|
|
// deliberate, not an oversight: a real client can only ever decide to
|
|
// start speaking OMEMO to us once it's already able to fetch our bundle,
|
|
// so serving must not be gated behind the same per-chat/account flag that
|
|
// gates outbound encryption (see the plan's enablement model).
|
|
func handleGetE2EEPubSubIq(s xmpp.Sender, iq *stanza.IQ, pubsub *stanza.PubSubGeneric) {
|
|
component, answer, ok := iqResultStub(s, iq)
|
|
if !ok {
|
|
return
|
|
}
|
|
defer gateway.ResumableSend(component, answer)
|
|
|
|
backend, ok := gateway.E2EE.Backend()
|
|
if !ok {
|
|
iqAnswerSetError(answer, 404)
|
|
return
|
|
}
|
|
|
|
_, toOk, toIsGroup := toToID(iq.To)
|
|
if !toOk || toIsGroup {
|
|
// e2ee identities only ever exist for personal-chat pseudo-JIDs -
|
|
// true MUC/XEP-0045 groupchat OMEMO is out of scope entirely (see
|
|
// the plan doc).
|
|
iqAnswerSetError(answer, 404)
|
|
return
|
|
}
|
|
toJid, err := stanza.NewJid(iq.To)
|
|
if err != nil {
|
|
iqAnswerSetError(answer, 400)
|
|
return
|
|
}
|
|
|
|
bare, _, fromOk := gateway.SplitJID(iq.From)
|
|
if !fromOk {
|
|
iqAnswerSetError(answer, 400)
|
|
return
|
|
}
|
|
session, sessionOk := sessions[bare]
|
|
if !sessionOk {
|
|
log.Errorf("E2EE PEP IQ from stranger %v", iq.From)
|
|
iqAnswerSetError(answer, 403)
|
|
return
|
|
}
|
|
|
|
owner := e2ee.OwnedPeer(session.Session.Login, toJid.Bare())
|
|
if err := backend.EnsureIdentity(owner); err != nil {
|
|
log.Error(errors.Wrap(err, "Failed to ensure e2ee identity"))
|
|
iqAnswerSetError(answer, 500)
|
|
return
|
|
}
|
|
|
|
for _, variant := range backend.Variants() {
|
|
if pubsub.Items.Node == backend.DeviceListNode(variant) {
|
|
doc, err := backend.PublishedIdentity(owner, variant)
|
|
if err != nil {
|
|
log.Error(errors.Wrap(err, "Failed to publish e2ee device list"))
|
|
iqAnswerSetError(answer, 500)
|
|
return
|
|
}
|
|
setE2EEItemsAnswer(answer, pubsub.Items.Node, "current", doc.Raw)
|
|
return
|
|
}
|
|
if pubsub.Items.Node == backend.BundleNode(variant, backend.OwnDevice()) {
|
|
doc, err := backend.PublishedBundle(owner, backend.OwnDevice(), variant)
|
|
if err != nil {
|
|
log.Error(errors.Wrap(err, "Failed to publish e2ee bundle"))
|
|
iqAnswerSetError(answer, 500)
|
|
return
|
|
}
|
|
setE2EEItemsAnswer(answer, pubsub.Items.Node, string(backend.OwnDevice()), doc.Raw)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Node didn't match any variant this backend currently supports.
|
|
iqAnswerSetError(answer, 404)
|
|
}
|
|
|
|
// setE2EEItemsAnswer embeds raw (an e2ee.DeviceListDoc/BundleDoc's already-
|
|
// marshaled XML) as the single <item> of a pubsub#items result - raw is
|
|
// re-parsed into a generic stanza.Node rather than injected as a string,
|
|
// since stanza.Item.Any expects a structured node, not raw bytes.
|
|
func setE2EEItemsAnswer(answer *stanza.IQ, node, itemID string, raw []byte) {
|
|
var content stanza.Node
|
|
if err := xml.Unmarshal(raw, &content); err != nil {
|
|
log.Error(errors.Wrap(err, "Failed to unmarshal e2ee document"))
|
|
iqAnswerSetError(answer, 500)
|
|
return
|
|
}
|
|
answer.Payload = &stanza.PubSubGeneric{
|
|
Items: &stanza.Items{
|
|
Node: node,
|
|
List: []stanza.Item{
|
|
{Id: itemID, Any: &content},
|
|
},
|
|
},
|
|
}
|
|
}
|