summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/rsc/xmpp/xmpp.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mattermost/rsc/xmpp/xmpp.go')
-rw-r--r--vendor/github.com/mattermost/rsc/xmpp/xmpp.go572
1 files changed, 572 insertions, 0 deletions
diff --git a/vendor/github.com/mattermost/rsc/xmpp/xmpp.go b/vendor/github.com/mattermost/rsc/xmpp/xmpp.go
new file mode 100644
index 000000000..6fa7e43cb
--- /dev/null
+++ b/vendor/github.com/mattermost/rsc/xmpp/xmpp.go
@@ -0,0 +1,572 @@
+// 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.
+
+// TODO(rsc):
+// More precise error handling.
+// Presence functionality.
+// TODO(mattn):
+// Add proxy authentication.
+
+// Package xmpp implements a simple Google Talk client
+// using the XMPP protocol described in RFC 3920 and RFC 3921.
+package xmpp
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "encoding/base64"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+)
+
+const (
+ nsStream = "http://etherx.jabber.org/streams"
+ nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
+ nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
+ nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
+ nsClient = "jabber:client"
+)
+
+var DefaultConfig tls.Config
+
+type Client struct {
+ tls *tls.Conn // connection to server
+ jid string // Jabber ID for our connection
+ p *xml.Decoder
+}
+
+// NewClient creates a new connection to a host given as "hostname" or "hostname:port".
+// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
+// Default the port to 5222.
+func NewClient(host, user, passwd string) (*Client, error) {
+ addr := host
+
+ if strings.TrimSpace(host) == "" {
+ a := strings.SplitN(user, "@", 2)
+ if len(a) == 2 {
+ host = a[1]
+ }
+ }
+ a := strings.SplitN(host, ":", 2)
+ if len(a) == 1 {
+ host += ":5222"
+ }
+ proxy := os.Getenv("HTTP_PROXY")
+ if proxy == "" {
+ proxy = os.Getenv("http_proxy")
+ }
+ if proxy != "" {
+ url, err := url.Parse(proxy)
+ if err == nil {
+ addr = url.Host
+ }
+ }
+ c, err := net.Dial("tcp", addr)
+ if err != nil {
+ return nil, err
+ }
+
+ if proxy != "" {
+ fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host)
+ fmt.Fprintf(c, "Host: %s\r\n", host)
+ fmt.Fprintf(c, "\r\n")
+ br := bufio.NewReader(c)
+ req, _ := http.NewRequest("CONNECT", host, nil)
+ resp, err := http.ReadResponse(br, req)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != 200 {
+ f := strings.SplitN(resp.Status, " ", 2)
+ return nil, errors.New(f[1])
+ }
+ }
+
+ tlsconn := tls.Client(c, &DefaultConfig)
+ if err = tlsconn.Handshake(); err != nil {
+ return nil, err
+ }
+
+ if strings.LastIndex(host, ":") > 0 {
+ host = host[:strings.LastIndex(host, ":")]
+ }
+ if err = tlsconn.VerifyHostname(host); err != nil {
+ return nil, err
+ }
+
+ client := new(Client)
+ client.tls = tlsconn
+ if err := client.init(user, passwd); err != nil {
+ client.Close()
+ return nil, err
+ }
+ return client, nil
+}
+
+func (c *Client) Close() error {
+ return c.tls.Close()
+}
+
+func (c *Client) init(user, passwd string) error {
+ // For debugging: the following causes the plaintext of the connection to be duplicated to stdout.
+ // c.p = xml.NewParser(tee{c.tls, os.Stdout});
+ c.p = xml.NewDecoder(c.tls)
+
+ a := strings.SplitN(user, "@", 2)
+ if len(a) != 2 {
+ return errors.New("xmpp: invalid username (want user@domain): " + user)
+ }
+ user = a[0]
+ domain := a[1]
+
+ // Declare intent to be a jabber client.
+ fmt.Fprintf(c.tls, "<?xml version='1.0'?>\n"+
+ "<stream:stream to='%s' xmlns='%s'\n"+
+ " xmlns:stream='%s' version='1.0'>\n",
+ xmlEscape(domain), nsClient, nsStream)
+
+ // Server should respond with a stream opening.
+ se, err := nextStart(c.p)
+ if err != nil {
+ return err
+ }
+ if se.Name.Space != nsStream || se.Name.Local != "stream" {
+ return errors.New("xmpp: expected <stream> but got <" + se.Name.Local + "> in " + se.Name.Space)
+ }
+
+ // Now we're in the stream and can use Unmarshal.
+ // Next message should be <features> to tell us authentication options.
+ // See section 4.6 in RFC 3920.
+ var f streamFeatures
+ if err = c.p.Decode(&f); err != nil {
+ return errors.New("unmarshal <features>: " + err.Error())
+ }
+ havePlain := false
+ for _, m := range f.Mechanisms.Mechanism {
+ if m == "PLAIN" {
+ havePlain = true
+ break
+ }
+ }
+ if !havePlain {
+ return errors.New(fmt.Sprintf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism))
+ }
+
+ // Plain authentication: send base64-encoded \x00 user \x00 password.
+ raw := "\x00" + user + "\x00" + passwd
+ enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
+ base64.StdEncoding.Encode(enc, []byte(raw))
+ fmt.Fprintf(c.tls, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n",
+ nsSASL, enc)
+
+ // Next message should be either success or failure.
+ name, val, err := next(c.p)
+ switch v := val.(type) {
+ case *saslSuccess:
+ case *saslFailure:
+ // v.Any is type of sub-element in failure,
+ // which gives a description of what failed.
+ return errors.New("auth failure: " + v.Any.Local)
+ default:
+ return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space)
+ }
+
+ // Now that we're authenticated, we're supposed to start the stream over again.
+ // Declare intent to be a jabber client.
+ fmt.Fprintf(c.tls, "<stream:stream to='%s' xmlns='%s'\n"+
+ " xmlns:stream='%s' version='1.0'>\n",
+ xmlEscape(domain), nsClient, nsStream)
+
+ // Here comes another <stream> and <features>.
+ se, err = nextStart(c.p)
+ if err != nil {
+ return err
+ }
+ if se.Name.Space != nsStream || se.Name.Local != "stream" {
+ return errors.New("expected <stream>, got <" + se.Name.Local + "> in " + se.Name.Space)
+ }
+ if err = c.p.Decode(&f); err != nil {
+ // TODO: often stream stop.
+ //return os.NewError("unmarshal <features>: " + err.String())
+ }
+
+ // Send IQ message asking to bind to the local user name.
+ fmt.Fprintf(c.tls, "<iq type='set' id='x'><bind xmlns='%s'/></iq>\n", nsBind)
+ var iq clientIQ
+ if err = c.p.Decode(&iq); err != nil {
+ return errors.New("unmarshal <iq>: " + err.Error())
+ }
+ if &iq.Bind == nil {
+ return errors.New("<iq> result missing <bind>")
+ }
+ c.jid = iq.Bind.Jid // our local id
+
+ // We're connected and can now receive and send messages.
+ c.Status(Away, "")
+ return nil
+}
+
+type Chat struct {
+ Remote string
+ Type string
+ Text string
+ Roster Roster
+ Presence *Presence
+}
+
+type Roster []Contact
+
+type Contact struct {
+ Remote string
+ Name string
+ Group []string
+}
+
+type Presence struct {
+ Remote string
+ Status Status
+ StatusMsg string
+ Priority int
+}
+
+func atoi(s string) int {
+ if s == "" {
+ return 0
+ }
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ n = -1
+ }
+ return n
+}
+
+func statusCode(s string) Status {
+ for i, ss := range statusName {
+ if s == ss {
+ return Status(i)
+ }
+ }
+ return Available
+}
+
+// Recv wait next token of chat.
+func (c *Client) Recv() (chat Chat, err error) {
+ for {
+ _, val, err := next(c.p)
+ if err != nil {
+ return Chat{}, err
+ }
+ switch val := val.(type) {
+ case *clientMessage:
+ return Chat{Remote: val.From, Type: val.Type, Text: val.Body}, nil
+ case *clientQuery:
+ var r Roster
+ for _, item := range val.Item {
+ r = append(r, Contact{item.Jid, item.Name, item.Group})
+ }
+ return Chat{Type: "roster", Roster: r}, nil
+ case *clientPresence:
+ pr := &Presence{Remote: val.From, Status: statusCode(val.Show), StatusMsg: val.Status, Priority: atoi(val.Priority)}
+ if val.Type == "unavailable" {
+ pr.Status = Unavailable
+ }
+ return Chat{Remote: val.From, Type: "presence", Presence: pr}, nil
+ default:
+ //log.Printf("ignoring %T", val)
+ }
+ }
+ panic("unreachable")
+}
+
+// Send sends message text.
+func (c *Client) Send(chat Chat) error {
+ fmt.Fprintf(c.tls, "<message to='%s' from='%s' type='chat' xml:lang='en'>"+
+ "<body>%s</body></message>",
+ xmlEscape(chat.Remote), xmlEscape(c.jid),
+ xmlEscape(chat.Text))
+ return nil
+}
+
+// Roster asks for the chat roster.
+func (c *Client) Roster() error {
+ fmt.Fprintf(c.tls, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
+ return nil
+}
+
+type Status int
+
+const (
+ Unavailable Status = iota
+ DoNotDisturb
+ ExtendedAway
+ Away
+ Available
+)
+
+var statusName = []string{
+ Unavailable: "unavailable",
+ DoNotDisturb: "dnd",
+ ExtendedAway: "xa",
+ Away: "away",
+ Available: "chat",
+}
+
+func (s Status) String() string {
+ return statusName[s]
+}
+
+func (c *Client) Status(status Status, msg string) error {
+ fmt.Fprintf(c.tls, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", status, xmlEscape(msg))
+ return nil
+}
+
+// RFC 3920 C.1 Streams name space
+
+type streamFeatures struct {
+ XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
+ StartTLS tlsStartTLS
+ Mechanisms saslMechanisms
+ Bind bindBind
+ Session bool
+}
+
+type streamError struct {
+ XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
+ Any xml.Name
+ Text string
+}
+
+// RFC 3920 C.3 TLS name space
+
+type tlsStartTLS struct {
+ XMLName xml.Name `xml:":ietf:params:xml:ns:xmpp-tls starttls"`
+ Required bool
+}
+
+type tlsProceed struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
+}
+
+type tlsFailure struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"`
+}
+
+// RFC 3920 C.4 SASL name space
+
+type saslMechanisms struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
+ Mechanism []string
+}
+
+type saslAuth struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
+ Mechanism string `xml:"attr"`
+}
+
+type saslChallenge string
+
+type saslResponse string
+
+type saslAbort struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"`
+}
+
+type saslSuccess struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
+}
+
+type saslFailure struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
+ Any xml.Name
+}
+
+// RFC 3920 C.5 Resource binding name space
+
+type bindBind struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
+ Resource string
+ Jid string
+}
+
+// RFC 3921 B.1 jabber:client
+
+type clientMessage struct {
+ XMLName xml.Name `xml:"jabber:client message"`
+ From string `xml:"attr"`
+ Id string `xml:"attr"`
+ To string `xml:"attr"`
+ Type string `xml:"attr"` // chat, error, groupchat, headline, or normal
+
+ // These should technically be []clientText,
+ // but string is much more convenient.
+ Subject string
+ Body string
+ Thread string
+}
+
+type clientText struct {
+ Lang string `xml:"attr"`
+ Body string `xml:"chardata"`
+}
+
+type clientPresence struct {
+ XMLName xml.Name `xml:"jabber:client presence"`
+ From string `xml:"attr"`
+ Id string `xml:"attr"`
+ To string `xml:"attr"`
+ Type string `xml:"attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed
+ Lang string `xml:"attr"`
+
+ Show string // away, chat, dnd, xa
+ Status string // sb []clientText
+ Priority string
+ Error *clientError
+}
+
+type clientIQ struct { // info/query
+ XMLName xml.Name `xml:"jabber:client iq"`
+ From string `xml:"attr"`
+ Id string `xml:"attr"`
+ To string `xml:"attr"`
+ Type string `xml:"attr"` // error, get, result, set
+ Error clientError
+ Bind bindBind
+ Query clientQuery
+}
+
+type clientError struct {
+ XMLName xml.Name `xml:"jabber:client error"`
+ Code string `xml:"attr"`
+ Type string `xml:"attr"`
+ Any xml.Name
+ Text string
+}
+
+type clientQuery struct {
+ Item []rosterItem
+}
+
+type rosterItem struct {
+ XMLName xml.Name `xml:"jabber:iq:roster item"`
+ Jid string `xml:"attr"`
+ Name string `xml:"attr"`
+ Subscription string `xml:"attr"`
+ Group []string
+}
+
+// Scan XML token stream to find next StartElement.
+func nextStart(p *xml.Decoder) (xml.StartElement, error) {
+ for {
+ t, err := p.Token()
+ if err != nil {
+ log.Fatal("token", err)
+ }
+ switch t := t.(type) {
+ case xml.StartElement:
+ return t, nil
+ }
+ }
+ panic("unreachable")
+}
+
+// Scan XML token stream for next element and save into val.
+// If val == nil, allocate new element based on proto map.
+// Either way, return val.
+func next(p *xml.Decoder) (xml.Name, interface{}, error) {
+ // Read start element to find out what type we want.
+ se, err := nextStart(p)
+ if err != nil {
+ return xml.Name{}, nil, err
+ }
+
+ // Put it in an interface and allocate one.
+ var nv interface{}
+ switch se.Name.Space + " " + se.Name.Local {
+ case nsStream + " features":
+ nv = &streamFeatures{}
+ case nsStream + " error":
+ nv = &streamError{}
+ case nsTLS + " starttls":
+ nv = &tlsStartTLS{}
+ case nsTLS + " proceed":
+ nv = &tlsProceed{}
+ case nsTLS + " failure":
+ nv = &tlsFailure{}
+ case nsSASL + " mechanisms":
+ nv = &saslMechanisms{}
+ case nsSASL + " challenge":
+ nv = ""
+ case nsSASL + " response":
+ nv = ""
+ case nsSASL + " abort":
+ nv = &saslAbort{}
+ case nsSASL + " success":
+ nv = &saslSuccess{}
+ case nsSASL + " failure":
+ nv = &saslFailure{}
+ case nsBind + " bind":
+ nv = &bindBind{}
+ case nsClient + " message":
+ nv = &clientMessage{}
+ case nsClient + " presence":
+ nv = &clientPresence{}
+ case nsClient + " iq":
+ nv = &clientIQ{}
+ case nsClient + " error":
+ nv = &clientError{}
+ default:
+ return xml.Name{}, nil, errors.New("unexpected XMPP message " +
+ se.Name.Space + " <" + se.Name.Local + "/>")
+ }
+
+ // Unmarshal into that storage.
+ if err = p.DecodeElement(nv, &se); err != nil {
+ return xml.Name{}, nil, err
+ }
+ return se.Name, nv, err
+}
+
+var xmlSpecial = map[byte]string{
+ '<': "&lt;",
+ '>': "&gt;",
+ '"': "&quot;",
+ '\'': "&apos;",
+ '&': "&amp;",
+}
+
+func xmlEscape(s string) string {
+ var b bytes.Buffer
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if s, ok := xmlSpecial[c]; ok {
+ b.WriteString(s)
+ } else {
+ b.WriteByte(c)
+ }
+ }
+ return b.String()
+}
+
+type tee struct {
+ r io.Reader
+ w io.Writer
+}
+
+func (t tee) Read(p []byte) (n int, err error) {
+ n, err = t.r.Read(p)
+ if n > 0 {
+ t.w.Write(p[0:n])
+ }
+ return
+}