diff --git a/go.mod b/go.mod index 4ba92501..3da58716 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d github.com/matterbridge/discordgo v0.18.1-0.20200308151012-aa40f01cbcc3 github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible - github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 + github.com/matterbridge/go-xmpp v0.0.0-20200328215643-8d36ff2c85d1 github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6 github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18 github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61 diff --git a/go.sum b/go.sum index bede12d2..e3e07622 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/matterbridge/discordgo v0.18.1-0.20200308151012-aa40f01cbcc3 h1:VP/DN github.com/matterbridge/discordgo v0.18.1-0.20200308151012-aa40f01cbcc3/go.mod h1:5a1bHtG/38ofcx9cgwM5eTW/Pl4SpbQksNDnTRcGA2Y= github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible h1:oaOqwbg5HxHRxvAbd84ks0Okwoc1ISyUZ87EiVJFhGI= github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible/go.mod h1:igE6rUAn3jai2wCdsjFHfhUoekjrFthoEjFObKKwSb4= -github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k= -github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q= +github.com/matterbridge/go-xmpp v0.0.0-20200328215643-8d36ff2c85d1 h1:1GBtdv3HbYTPbGP3y/QpJ7S4ogB5gs/+gGKhj4ri2CM= +github.com/matterbridge/go-xmpp v0.0.0-20200328215643-8d36ff2c85d1/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q= github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6 h1:Kl65VJv38HjYFnnwH+MP6Z8hcJT5UHuSpHVU5vW1HH0= github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6/go.mod h1:+jWeaaUtXQbBRdKYWfjW6JDDYiI2XXE+3NnTjW5kg8g= github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18 h1:fLhwXtWGtfTgZVxHG1lcKjv+re7dRwyyuYFNu69xdho= diff --git a/vendor/github.com/matterbridge/go-xmpp/xmpp.go b/vendor/github.com/matterbridge/go-xmpp/xmpp.go index 39aa2d8c..cc13218e 100644 --- a/vendor/github.com/matterbridge/go-xmpp/xmpp.go +++ b/vendor/github.com/matterbridge/go-xmpp/xmpp.go @@ -1,4 +1,4 @@ -// Copyright 2011 The Go Authors. All rights reserved. +// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -45,6 +45,9 @@ const ( // Default TLS configuration options var DefaultConfig tls.Config +// DebugWriter is the writer used to write debugging output to. +var DebugWriter io.Writer = os.Stderr + // Cookie is a unique XMPP session identifier type Cookie uint64 @@ -191,21 +194,40 @@ type Options struct { // Status message StatusMessage string - - // Logger - Logger io.Writer } // NewClient establishes a new Client connection based on a set of Options. func (o Options) NewClient() (*Client, error) { host := o.Host + if strings.TrimSpace(host) == "" { + a := strings.SplitN(o.User, "@", 2) + if len(a) == 2 { + if _, addrs, err := net.LookupSRV("xmpp-client", "tcp", a[1]); err == nil { + if len(addrs) > 0 { + // default to first record + host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port) + defP := addrs[0].Priority + for _, adr := range addrs { + if adr.Priority < defP { + host = fmt.Sprintf("%s:%d", adr.Target, adr.Port) + defP = adr.Priority + } + } + } else { + host = a[1] + } + } else { + host = a[1] + } + } + } c, err := connect(host, o.User, o.Password) if err != nil { return nil, err } - if strings.LastIndex(o.Host, ":") > 0 { - host = host[:strings.LastIndex(o.Host, ":")] + if strings.LastIndex(host, ":") > 0 { + host = host[:strings.LastIndex(host, ":")] } client := new(Client) @@ -487,6 +509,8 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string case f.StartTLS == nil: // the server does not support STARTTLS return f, nil + case !o.StartTLS && f.StartTLS.Required == nil: + return f, nil case f.StartTLS.Required != nil: // the server requires STARTTLS. case !o.StartTLS: @@ -527,7 +551,7 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string // will be returned. func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) { if o.Debug { - c.p = xml.NewDecoder(tee{c.conn, o.Logger}) + c.p = xml.NewDecoder(tee{c.conn, DebugWriter}) } else { c.p = xml.NewDecoder(c.conn) } @@ -618,6 +642,11 @@ func (c *Client) Recv() (stanza interface{}, err error) { } switch v := val.(type) { case *clientMessage: + if v.Event.XMLNS == XMPPNS_PUBSUB_EVENT { + // Handle Pubsub notifications + return pubsubClientToReturn(v.Event), nil + } + stamp, _ := time.Parse( "2006-01-02T15:04:05Z", v.Delay.Stamp, @@ -644,25 +673,101 @@ func (c *Client) Recv() (stanza interface{}, err error) { case *clientPresence: return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil case *clientIQ: - // TODO check more strictly - if bytes.Equal(bytes.TrimSpace(v.Query), []byte(``)) || bytes.Equal(bytes.TrimSpace(v.Query), []byte(``)) { + switch { + case v.Query.XMLName.Space == "urn:xmpp:ping": + // TODO check more strictly err := c.SendResultPing(v.ID, v.From) if err != nil { return Chat{}, err } + fallthrough + case v.Type == "error": + switch v.ID { + case "sub1": + // Pubsub subscription failed + var errs []clientPubsubError + err := xml.Unmarshal([]byte(v.Error.InnerXML), &errs) + if err != nil { + return PubsubSubscription{}, err + } + + var errsStr []string + for _, e := range errs { + errsStr = append(errsStr, e.XMLName.Local) + } + + return PubsubSubscription{ + Errors: errsStr, + }, nil + } + case v.Type == "result" && v.ID == "unsub1": + // Unsubscribing MAY contain a pubsub element. But it does + // not have to + return PubsubUnsubscription{ + SubID: "", + JID: v.From, + Node: "", + Errors: nil, + }, nil + case v.Query.XMLName.Local == "pubsub": + switch v.ID { + case "sub1": + // Subscription or unsubscription was successful + var sub clientPubsubSubscription + err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) + if err != nil { + return PubsubSubscription{}, err + } + + return PubsubSubscription{ + SubID: sub.SubID, + JID: sub.JID, + Node: sub.Node, + Errors: nil, + }, nil + case "unsub1": + var sub clientPubsubSubscription + err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) + if err != nil { + return PubsubUnsubscription{}, err + } + + return PubsubUnsubscription{ + SubID: sub.SubID, + JID: v.From, + Node: sub.Node, + Errors: nil, + }, nil + case "items1", "items3": + var p clientPubsubItems + err := xml.Unmarshal([]byte(v.Query.InnerXML), &p) + if err != nil { + return PubsubItems{}, err + } + + return PubsubItems{ + p.Node, + pubsubItemsToReturn(p.Items), + }, nil + } + case v.Query.XMLName.Local == "": + return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type}, nil + default: + res, err := xml.Marshal(v.Query) + if err != nil { + return Chat{}, err + } + + return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, + Query: res}, nil } - return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil } } } // Send sends the message wrapped inside an XMPP message stanza body. func (c *Client) Send(chat Chat) (n int, err error) { - var subtext = `` - var thdtext = `` - var oobtext = `` - var msgidtext = `` - var msgcorrecttext = `` + var subtext, thdtext, oobtext, msgidtext, msgcorrecttext string if chat.Subject != `` { subtext = `` + xmlEscape(chat.Subject) + `` } @@ -676,20 +781,25 @@ func (c *Client) Send(chat Chat) (n int, err error) { } oobtext += `` } + if chat.ID != `` { msgidtext = `id='` + xmlEscape(chat.ID) + `'` + } else { + msgidtext = `id='` + cnonce() + `'` } + if chat.ReplaceID != `` { msgcorrecttext = `` } - return fmt.Fprintf(c.conn, "" + subtext + "%s" + msgcorrecttext + oobtext + thdtext + "", - xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) + + stanza := "" + subtext + "%s" + msgcorrecttext + oobtext + thdtext + "" + + return fmt.Fprintf(c.conn, stanza, xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) } // SendOOB sends OOB data wrapped inside an XMPP message stanza, without actual body. func (c *Client) SendOOB(chat Chat) (n int, err error) { - var thdtext = `` - var oobtext = `` + var thdtext, oobtext string if chat.Thread != `` { thdtext = `` + xmlEscape(chat.Thread) + `` } @@ -700,8 +810,8 @@ func (c *Client) SendOOB(chat Chat) (n int, err error) { } oobtext += `` } - return fmt.Fprintf(c.conn, "" + oobtext + thdtext + "", - xmlEscape(chat.Remote), xmlEscape(chat.Type)) + return fmt.Fprintf(c.conn, ""+oobtext+thdtext+"", + xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce()) } // SendOrg sends the original text without being wrapped in an XMPP message stanza. @@ -800,8 +910,8 @@ type bindBind struct { } type clientMessageCorrect struct { - XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"` - ID string `xml:"id,attr"` + XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"` + ID string `xml:"id,attr"` } // RFC 3921 B.1 jabber:client @@ -813,11 +923,14 @@ type clientMessage struct { Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal // These should technically be []clientText, but string is much more convenient. - Subject string `xml:"subject"` - Body string `xml:"body"` - Thread string `xml:"thread"` + Subject string `xml:"subject"` + Body string `xml:"body"` + Thread string `xml:"thread"` ReplaceID clientMessageCorrect + // Pubsub + Event clientPubsubEvent `xml:"event"` + // Any hasn't matched element Other []XMLElement `xml:",any"` @@ -882,23 +995,25 @@ type clientPresence struct { Error *clientError } -type clientIQ struct { // info/query - XMLName xml.Name `xml:"jabber:client iq"` - From string `xml:"from,attr"` - ID string `xml:"id,attr"` - To string `xml:"to,attr"` - Type string `xml:"type,attr"` // error, get, result, set - Query []byte `xml:",innerxml"` +type clientIQ struct { + // info/query + XMLName xml.Name `xml:"jabber:client iq"` + From string `xml:"from,attr"` + ID string `xml:"id,attr"` + To string `xml:"to,attr"` + Type string `xml:"type,attr"` // error, get, result, set + Query XMLElement `xml:",any"` Error clientError Bind bindBind } type clientError struct { - XMLName xml.Name `xml:"jabber:client error"` - Code string `xml:",attr"` - Type string `xml:",attr"` - Any xml.Name - Text string + XMLName xml.Name `xml:"jabber:client error"` + Code string `xml:",attr"` + Type string `xml:"type,attr"` + Any xml.Name + InnerXML []byte `xml:",innerxml"` + Text string } type clientQuery struct { diff --git a/vendor/github.com/matterbridge/go-xmpp/xmpp_muc.go b/vendor/github.com/matterbridge/go-xmpp/xmpp_muc.go index 7b50c128..840f4f97 100644 --- a/vendor/github.com/matterbridge/go-xmpp/xmpp_muc.go +++ b/vendor/github.com/matterbridge/go-xmpp/xmpp_muc.go @@ -8,19 +8,19 @@ package xmpp import ( + "errors" "fmt" "time" - "errors" ) const ( - nsMUC = "http://jabber.org/protocol/muc" - nsMUCUser = "http://jabber.org/protocol/muc#user" - NoHistory = 0 - CharHistory = 1 - StanzaHistory = 2 + nsMUC = "http://jabber.org/protocol/muc" + nsMUCUser = "http://jabber.org/protocol/muc#user" + NoHistory = 0 + CharHistory = 1 + StanzaHistory = 2 SecondsHistory = 3 - SinceHistory = 4 + SinceHistory = 4 ) // Send sends room topic wrapped inside an XMPP message stanza body. @@ -47,35 +47,35 @@ func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_da } switch history_type { case NoHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC) + xmlEscape(jid), xmlEscape(nick), nsMUC) case CharHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, history) + xmlEscape(jid), xmlEscape(nick), nsMUC, history) case StanzaHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, history) + xmlEscape(jid), xmlEscape(nick), nsMUC, history) case SecondsHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, history) + xmlEscape(jid), xmlEscape(nick), nsMUC, history) case SinceHistory: if history_date != nil { - return fmt.Fprintf(c.conn, "\n" + - "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ + "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339)) + xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339)) } } return 0, errors.New("Unknown history option") @@ -88,41 +88,41 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ } switch history_type { case NoHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + - "%s" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ + "%s"+ + "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password)) + xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password)) case CharHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "%s\n"+ "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) + xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) case StanzaHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "%s\n"+ "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) + xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) case SecondsHistory: - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "%s\n"+ "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) + xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) case SinceHistory: if history_date != nil { - return fmt.Fprintf(c.conn, "\n" + - "\n" + + return fmt.Fprintf(c.conn, "\n"+ + "\n"+ "%s\n"+ - "\n" + + "\n"+ "", - xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339)) + xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339)) } } return 0, errors.New("Unknown history option") diff --git a/vendor/github.com/matterbridge/go-xmpp/xmpp_pubsub.go b/vendor/github.com/matterbridge/go-xmpp/xmpp_pubsub.go new file mode 100644 index 00000000..74e60d86 --- /dev/null +++ b/vendor/github.com/matterbridge/go-xmpp/xmpp_pubsub.go @@ -0,0 +1,133 @@ +package xmpp + +import ( + "encoding/xml" + "fmt" +) + +const ( + XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub" + XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event" +) + +type clientPubsubItem struct { + XMLName xml.Name `xml:"item"` + ID string `xml:"id,attr"` + Body []byte `xml:",innerxml"` +} + +type clientPubsubItems struct { + XMLName xml.Name `xml:"items"` + Node string `xml:"node,attr"` + Items []clientPubsubItem `xml:"item"` +} + +type clientPubsub struct { + XMLName xml.Name `xml:"pubsub"` + Items clientPubsubItems `xml:"items"` +} + +type clientPubsubEvent struct { + XMLName xml.Name `xml:"event"` + XMLNS string `xml:"xmlns,attr"` + Items clientPubsubItems `xml:"items"` +} + +type clientPubsubError struct { + XMLName xml.Name +} + +type clientPubsubSubscription struct { + XMLName xml.Name `xml:"subscription"` + Node string `xml:"node,attr"` + JID string `xml:"jid,attr"` + SubID string `xml:"subid,attr"` +} + +type PubsubEvent struct { + Node string + Items []PubsubItem +} + +type PubsubSubscription struct { + SubID string + JID string + Node string + Errors []string +} +type PubsubUnsubscription PubsubSubscription + +type PubsubItem struct { + ID string + InnerXML []byte +} + +type PubsubItems struct { + Node string + Items []PubsubItem +} + +// Converts []clientPubsubItem to []PubsubItem +func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem { + var tmp []PubsubItem + for _, i := range items { + tmp = append(tmp, PubsubItem{ + ID: i.ID, + InnerXML: i.Body, + }) + } + + return tmp +} + +func pubsubClientToReturn(event clientPubsubEvent) PubsubEvent { + return PubsubEvent{ + Node: event.Items.Node, + Items: pubsubItemsToReturn(event.Items.Items), + } +} + +func pubsubStanza(body string) string { + return fmt.Sprintf("%s", + XMPPNS_PUBSUB, body) +} + +func pubsubSubscriptionStanza(node, jid string) string { + body := fmt.Sprintf("", + xmlEscape(node), + xmlEscape(jid)) + return pubsubStanza(body) +} + +func pubsubUnsubscriptionStanza(node, jid string) string { + body := fmt.Sprintf("", + xmlEscape(node), + xmlEscape(jid)) + return pubsubStanza(body) +} + +func (c *Client) PubsubSubscribeNode(node, jid string) { + c.RawInformation(c.jid, + jid, + "sub1", + "set", + pubsubSubscriptionStanza(node, c.jid)) +} + +func (c *Client) PubsubUnsubscribeNode(node, jid string) { + c.RawInformation(c.jid, + jid, + "unsub1", + "set", + pubsubUnsubscriptionStanza(node, c.jid)) +} + +func (c *Client) PubsubRequestLastItems(node, jid string) { + body := fmt.Sprintf("", node) + c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body)) +} + +func (c *Client) PubsubRequestItem(node, jid, id string) { + body := fmt.Sprintf("", node, id) + c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body)) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a82a16bd..b63c1861 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -103,7 +103,7 @@ github.com/matterbridge/Rocket.Chat.Go.SDK/rest github.com/matterbridge/discordgo # github.com/matterbridge/emoji v2.1.1-0.20191117213217-af507f6b02db+incompatible github.com/matterbridge/emoji -# github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 +# github.com/matterbridge/go-xmpp v0.0.0-20200328215643-8d36ff2c85d1 github.com/matterbridge/go-xmpp # github.com/matterbridge/gomatrix v0.0.0-20200209224845-c2104d7936a6 github.com/matterbridge/gomatrix