--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/xmpp.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,261 @@
+// 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.
+
+// This package implements a simple XMPP client according to RFCs 3920
+// and 3921, plus the various XEPs at http://xmpp.org/protocols/. The
+// implementation is structured as a stack of layers, with TCP at the
+// bottom and the application at the top. The application receives and
+// sends structures representing XMPP stanzas. Additional stanza
+// parsers can be inserted into the stack of layers as extensions.
+package xmpp
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "sync"
+)
+
+const (
+ // Version of RFC 3920 that we implement.
+ XMPPVersion = "1.0"
+
+ // Various XML namespaces.
+ NsClient = "jabber:client"
+ NsStreams = "urn:ietf:params:xml:ns:xmpp-streams"
+ 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"
+ NsSession = "urn:ietf:params:xml:ns:xmpp-session"
+ NsRoster = "jabber:iq:roster"
+
+ // DNS SRV names
+ serverSrv = "xmpp-server"
+ clientSrv = "xmpp-client"
+)
+
+// A filter can modify the XMPP traffic to or from the remote
+// server. It's part of an Extension. The filter function will be
+// called in a new goroutine, so it doesn't need to return. The filter
+// should close its output when its input is closed.
+type Filter func(in <-chan Stanza, out chan<- Stanza)
+
+// Extensions can add stanza filters and/or new XML element types.
+type Extension struct {
+ // Maps from an XML namespace to a function which constructs a
+ // structure to hold the contents of stanzas in that
+ // namespace.
+ StanzaHandlers map[string]func(*xml.Name) interface{}
+ // If non-nil, will be called once to start the filter
+ // running. RecvFilter intercepts incoming messages on their
+ // way from the remote server to the application; SendFilter
+ // intercepts messages going the other direction.
+ RecvFilter Filter
+ SendFilter Filter
+}
+
+// Allows the user to override the TLS configuration.
+var TlsConfig tls.Config
+
+// The client in a client-server XMPP connection.
+type Client struct {
+ // This client's JID. This will be updated asynchronously by
+ // the time StartSession() returns.
+ Jid JID
+ password string
+ socket net.Conn
+ socketSync sync.WaitGroup
+ saslExpected string
+ authDone bool
+ handlers chan *stanzaHandler
+ inputControl chan int
+ // Incoming XMPP stanzas from the remote will be published on
+ // this channel. Information which is used by this library to
+ // set up the XMPP stream will not appear here.
+ In <-chan Stanza
+ // Outgoing XMPP stanzas to the server should be sent to this
+ // channel.
+ Out chan<- Stanza
+ xmlOut chan<- interface{}
+ // The client's roster is also known as the buddy list. It's
+ // the set of contacts which are known to this JID, or which
+ // this JID is known to.
+ Roster Roster
+ // Features advertised by the remote. This will be updated
+ // asynchronously as new features are received throughout the
+ // connection process. It should not be updated once
+ // StartSession() returns.
+ Features *Features
+ sendFilterAdd, recvFilterAdd chan Filter
+}
+
+// Connect to the appropriate server and authenticate as the given JID
+// with the given password. This function will return as soon as a TCP
+// connection has been established, but before XMPP stream negotiation
+// has completed. The negotiation will occur asynchronously, and any
+// send operation to Client.Out will block until negotiation (resource
+// binding) is complete.
+func NewClient(jid *JID, password string, exts []Extension) (*Client, error) {
+ // Include the mandatory extensions.
+ roster := newRosterExt()
+ exts = append(exts, roster.Extension)
+ exts = append(exts, bindExt)
+
+ // Resolve the domain in the JID.
+ _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
+ if err != nil {
+ return nil, errors.New("LookupSrv " + jid.Domain +
+ ": " + err.Error())
+ }
+
+ var tcp *net.TCPConn
+ for _, srv := range srvs {
+ addrStr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
+ addr, err := net.ResolveTCPAddr("tcp", addrStr)
+ if err != nil {
+ err = fmt.Errorf("ResolveTCPAddr(%s): %s",
+ addrStr, err.Error())
+ continue
+ }
+ tcp, err = net.DialTCP("tcp", nil, addr)
+ if err == nil {
+ break
+ }
+ err = fmt.Errorf("DialTCP(%s): %s", addr, err)
+ }
+ if tcp == nil {
+ return nil, err
+ }
+
+ cl := new(Client)
+ cl.Roster = *roster
+ cl.password = password
+ cl.Jid = *jid
+ cl.socket = tcp
+ cl.handlers = make(chan *stanzaHandler, 100)
+ cl.inputControl = make(chan int)
+
+ extStanza := make(map[string]func(*xml.Name) interface{})
+ for _, ext := range exts {
+ for k, v := range ext.StanzaHandlers {
+ extStanza[k] = v
+ }
+ }
+
+ // Start the transport handler, initially unencrypted.
+ recvReader, recvWriter := io.Pipe()
+ sendReader, sendWriter := io.Pipe()
+ go cl.readTransport(recvWriter)
+ go cl.writeTransport(sendReader)
+
+ // Start the reader and writer that convert to and from XML.
+ recvXml := make(chan interface{})
+ go readXml(recvReader, recvXml, extStanza)
+ sendXml := make(chan interface{})
+ cl.xmlOut = sendXml
+ go writeXml(sendWriter, sendXml)
+
+ // Start the reader and writer that convert between XML and
+ // XMPP stanzas.
+ recvRawXmpp := make(chan Stanza)
+ go cl.readStream(recvXml, recvRawXmpp)
+ sendRawXmpp := make(chan Stanza)
+ go writeStream(sendXml, sendRawXmpp, cl.inputControl)
+
+ // Start the manager for the filters that can modify what the
+ // app sees.
+ recvFiltXmpp := make(chan Stanza)
+ cl.In = recvFiltXmpp
+ go filterMgr(cl.recvFilterAdd, recvRawXmpp, recvFiltXmpp)
+ sendFiltXmpp := make(chan Stanza)
+ cl.Out = sendFiltXmpp
+ go filterMgr(cl.sendFilterAdd, sendFiltXmpp, sendFiltXmpp)
+
+ // Initial handshake.
+ hsOut := &stream{To: jid.Domain, Version: XMPPVersion}
+ cl.xmlOut <- hsOut
+
+ return cl, nil
+}
+
+func tee(r io.Reader, w io.Writer, prefix string) {
+ defer func(w io.Writer) {
+ if c, ok := w.(io.Closer); ok {
+ c.Close()
+ }
+ }(w)
+
+ buf := bytes.NewBuffer([]uint8(prefix))
+ for {
+ var c [1]byte
+ n, _ := r.Read(c[:])
+ if n == 0 {
+ break
+ }
+ n, _ = w.Write(c[:n])
+ if n == 0 {
+ break
+ }
+ buf.Write(c[:n])
+ if c[0] == '\n' || c[0] == '>' {
+ Debug.Log(buf)
+ buf = bytes.NewBuffer([]uint8(prefix))
+ }
+ }
+ leftover := buf.String()
+ if leftover != "" {
+ Debug.Log(buf)
+ }
+}
+
+// bindDone is called when we've finished resource binding (and all
+// the negotiations that precede it). Now we can start accepting
+// traffic from the app.
+func (cl *Client) bindDone() {
+ cl.inputControl <- 1
+}
+
+// Start an XMPP session. A typical XMPP client should call this
+// immediately after creating the Client in order to start the
+// session, retrieve the roster, and broadcast an initial
+// presence. The presence can be as simple as a newly-initialized
+// Presence struct. See RFC 3921, Section 3. After calling this, a
+// normal client will want to call Roster.Update().
+func (cl *Client) StartSession(pr *Presence) error {
+ id := NextId()
+ iq := &Iq{Header: Header{To: cl.Jid.Domain, Id: id, Type: "set",
+ Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}}
+ ch := make(chan error)
+ f := func(st Stanza) bool {
+ iq, ok := st.(*Iq)
+ if !ok {
+ Warn.Log("iq reply not iq; can't start session")
+ ch <- errors.New("bad session start reply")
+ return false
+ }
+ if iq.Type == "error" {
+ Warn.Logf("Can't start session: %v", iq)
+ ch <- iq.Error
+ return false
+ }
+ ch <- nil
+ return false
+ }
+ cl.HandleStanza(id, f)
+ cl.Out <- iq
+
+ // Now wait until the callback is called.
+ if err := <-ch; err != nil {
+ return err
+ }
+ if pr != nil {
+ cl.Out <- pr
+ }
+ return nil
+}