diff -r f464f14e39a7 -r 367e76b3028e xmpp.go --- a/xmpp.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,261 +0,0 @@ -// 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 -}