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 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}, }, }, } }