|
|
|
@ -1,8 +1,13 @@
|
|
|
|
|
package gateway
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"encoding/xml"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
@ -37,6 +42,19 @@ var DirtySessions = false
|
|
|
|
|
// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs
|
|
|
|
|
var MessageOutgoingPermission = false
|
|
|
|
|
|
|
|
|
|
// CapsType is a capability category
|
|
|
|
|
type CapsType int
|
|
|
|
|
const (
|
|
|
|
|
CapsAudio CapsType = iota
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ContactType is a disco JID category
|
|
|
|
|
type ContactType int
|
|
|
|
|
const (
|
|
|
|
|
ContactTransport CapsType = iota
|
|
|
|
|
ContactPM
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// SendMessage creates and sends a message stanza
|
|
|
|
|
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isOutgoing bool) {
|
|
|
|
|
sendMessageWrapper(to, from, body, id, component, reply, "", isOutgoing)
|
|
|
|
@ -225,6 +243,9 @@ var SPResource = args.NewString()
|
|
|
|
|
// SPImmed skips queueing
|
|
|
|
|
var SPImmed = args.NewBool(args.Default(true))
|
|
|
|
|
|
|
|
|
|
// SPCaps is a XEP-0115 verification string
|
|
|
|
|
var SPCaps = args.NewString()
|
|
|
|
|
|
|
|
|
|
func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
|
|
|
|
var presenceFrom string
|
|
|
|
|
if SPFrom.IsSet(args) {
|
|
|
|
@ -280,6 +301,16 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if SPCaps.IsSet(args) {
|
|
|
|
|
ver := SPCaps.Get(args)
|
|
|
|
|
if ver != "" {
|
|
|
|
|
presence.Extensions = append(presence.Extensions, extensions.CapsExtension{
|
|
|
|
|
Hash: "sha-1",
|
|
|
|
|
Node: "https://dev.narayana.im/narayana/telegabber/",
|
|
|
|
|
Ver: ver,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return presence
|
|
|
|
|
}
|
|
|
|
@ -356,3 +387,104 @@ func SplitJID(from string) (string, string, bool) {
|
|
|
|
|
}
|
|
|
|
|
return fromJid.Bare(), fromJid.Resource, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getDiscoFeatures(caps []CapsType) []string {
|
|
|
|
|
features := []string{
|
|
|
|
|
"http://jabber.org/protocol/caps",
|
|
|
|
|
"http://jabber.org/protocol/disco#info",
|
|
|
|
|
}
|
|
|
|
|
for typ := range features {
|
|
|
|
|
switch typ {
|
|
|
|
|
case CapsAudio:
|
|
|
|
|
features = append(
|
|
|
|
|
features,
|
|
|
|
|
"urn:xmpp:jingle-message:0",
|
|
|
|
|
"urn:xmpp:jingle:1",
|
|
|
|
|
"urn:xmpp:jingle:apps:dtls:0",
|
|
|
|
|
"urn:xmpp:jingle:apps:rtp:1",
|
|
|
|
|
"urn:xmpp:jingle:apps:rtp:audio",
|
|
|
|
|
"urn:xmpp:jingle:transports:ice-udp:1",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return features
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetDiscoInfo generates a disco info IQ query response
|
|
|
|
|
func GetDiscoInfo(typ ContactType, features []string) *stanza.DiscoInfo {
|
|
|
|
|
disco := stanza.DiscoInfo{}
|
|
|
|
|
if typ == ContactPM {
|
|
|
|
|
disco.AddIdentity("", "account", "registered")
|
|
|
|
|
} else {
|
|
|
|
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
|
|
|
|
}
|
|
|
|
|
disco.AddFeatures(features...)
|
|
|
|
|
return &disco
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// GetCapsVer hashes a capabilities set into a verification string
|
|
|
|
|
func GetCapsVer(caps []CapsType) (string, error) {
|
|
|
|
|
features := getDiscoFeatures(caps)
|
|
|
|
|
disco := GetDiscoInfo(features)
|
|
|
|
|
discoToCapsHash(disco)
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
binval := base64.NewEncoder(base64.StdEncoding, buf)
|
|
|
|
|
_, err = io.Copy(binval, file)
|
|
|
|
|
binval.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "Error calculating caps base64")
|
|
|
|
|
}
|
|
|
|
|
return buf.String(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func iOctetComparator(a, b string) bool {
|
|
|
|
|
return a < b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func discoToCaps(disco *stanza.DiscoInfo) string {
|
|
|
|
|
var s strings.Builder
|
|
|
|
|
var identities, vars, capsForms []string
|
|
|
|
|
|
|
|
|
|
for _, identity := range disco.Identity {
|
|
|
|
|
identities = append(identities, fmt.Sprintf(
|
|
|
|
|
"%s/%s//%s",
|
|
|
|
|
identity.Category,
|
|
|
|
|
identity.Type,
|
|
|
|
|
identity.Name,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(identities, iOctetComparator)
|
|
|
|
|
for _, identity := range identities {
|
|
|
|
|
s.WriteString(identity)
|
|
|
|
|
s.WriteString(">")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, feature := range disco.Features {
|
|
|
|
|
vars = append(vars, feature.Var)
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(vars, iOctetComparator)
|
|
|
|
|
for _, var := range vars {
|
|
|
|
|
s.WriteString(var)
|
|
|
|
|
s.WriteString(">")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if disco.Form != nil {
|
|
|
|
|
fields := make([]*stanza.Field, len(disco.Form.Fields))
|
|
|
|
|
copy(fields, disco.Form.Fields)
|
|
|
|
|
sort.Slice(fields, func(a, b *stanza.Field) bool {
|
|
|
|
|
if a.Var == "FORM_TYPE" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if b.Var == "FORM_TYPE" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return a.Var < b.Var
|
|
|
|
|
})
|
|
|
|
|
for _, field := range fields {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s.String()
|
|
|
|
|
}
|
|
|
|
|