xmpp/xmpp.go
changeset 126 367e76b3028e
parent 123 42a9995faa38
child 127 a8f9a0c07fc8
equal deleted inserted replaced
125:f464f14e39a7 126:367e76b3028e
       
     1 // Copyright 2011 The Go Authors.  All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style
       
     3 // license that can be found in the LICENSE file.
       
     4 
       
     5 // This package implements a simple XMPP client according to RFCs 3920
       
     6 // and 3921, plus the various XEPs at http://xmpp.org/protocols/. The
       
     7 // implementation is structured as a stack of layers, with TCP at the
       
     8 // bottom and the application at the top. The application receives and
       
     9 // sends structures representing XMPP stanzas. Additional stanza
       
    10 // parsers can be inserted into the stack of layers as extensions.
       
    11 package xmpp
       
    12 
       
    13 import (
       
    14 	"bytes"
       
    15 	"crypto/tls"
       
    16 	"encoding/xml"
       
    17 	"errors"
       
    18 	"fmt"
       
    19 	"io"
       
    20 	"net"
       
    21 	"sync"
       
    22 )
       
    23 
       
    24 const (
       
    25 	// Version of RFC 3920 that we implement.
       
    26 	XMPPVersion = "1.0"
       
    27 
       
    28 	// Various XML namespaces.
       
    29 	NsClient  = "jabber:client"
       
    30 	NsStreams = "urn:ietf:params:xml:ns:xmpp-streams"
       
    31 	NsStream  = "http://etherx.jabber.org/streams"
       
    32 	NsTLS     = "urn:ietf:params:xml:ns:xmpp-tls"
       
    33 	NsSASL    = "urn:ietf:params:xml:ns:xmpp-sasl"
       
    34 	NsBind    = "urn:ietf:params:xml:ns:xmpp-bind"
       
    35 	NsSession = "urn:ietf:params:xml:ns:xmpp-session"
       
    36 	NsRoster  = "jabber:iq:roster"
       
    37 
       
    38 	// DNS SRV names
       
    39 	serverSrv = "xmpp-server"
       
    40 	clientSrv = "xmpp-client"
       
    41 )
       
    42 
       
    43 // A filter can modify the XMPP traffic to or from the remote
       
    44 // server. It's part of an Extension. The filter function will be
       
    45 // called in a new goroutine, so it doesn't need to return. The filter
       
    46 // should close its output when its input is closed.
       
    47 type Filter func(in <-chan Stanza, out chan<- Stanza)
       
    48 
       
    49 // Extensions can add stanza filters and/or new XML element types.
       
    50 type Extension struct {
       
    51 	// Maps from an XML namespace to a function which constructs a
       
    52 	// structure to hold the contents of stanzas in that
       
    53 	// namespace.
       
    54 	StanzaHandlers map[string]func(*xml.Name) interface{}
       
    55 	// If non-nil, will be called once to start the filter
       
    56 	// running. RecvFilter intercepts incoming messages on their
       
    57 	// way from the remote server to the application; SendFilter
       
    58 	// intercepts messages going the other direction.
       
    59 	RecvFilter Filter
       
    60 	SendFilter Filter
       
    61 }
       
    62 
       
    63 // Allows the user to override the TLS configuration.
       
    64 var TlsConfig tls.Config
       
    65 
       
    66 // The client in a client-server XMPP connection.
       
    67 type Client struct {
       
    68 	// This client's JID. This will be updated asynchronously by
       
    69 	// the time StartSession() returns.
       
    70 	Jid          JID
       
    71 	password     string
       
    72 	socket       net.Conn
       
    73 	socketSync   sync.WaitGroup
       
    74 	saslExpected string
       
    75 	authDone     bool
       
    76 	handlers     chan *stanzaHandler
       
    77 	inputControl chan int
       
    78 	// Incoming XMPP stanzas from the remote will be published on
       
    79 	// this channel. Information which is used by this library to
       
    80 	// set up the XMPP stream will not appear here.
       
    81 	In <-chan Stanza
       
    82 	// Outgoing XMPP stanzas to the server should be sent to this
       
    83 	// channel.
       
    84 	Out    chan<- Stanza
       
    85 	xmlOut chan<- interface{}
       
    86 	// The client's roster is also known as the buddy list. It's
       
    87 	// the set of contacts which are known to this JID, or which
       
    88 	// this JID is known to.
       
    89 	Roster Roster
       
    90 	// Features advertised by the remote. This will be updated
       
    91 	// asynchronously as new features are received throughout the
       
    92 	// connection process. It should not be updated once
       
    93 	// StartSession() returns.
       
    94 	Features  *Features
       
    95 	sendFilterAdd, recvFilterAdd chan Filter
       
    96 }
       
    97 
       
    98 // Connect to the appropriate server and authenticate as the given JID
       
    99 // with the given password. This function will return as soon as a TCP
       
   100 // connection has been established, but before XMPP stream negotiation
       
   101 // has completed. The negotiation will occur asynchronously, and any
       
   102 // send operation to Client.Out will block until negotiation (resource
       
   103 // binding) is complete.
       
   104 func NewClient(jid *JID, password string, exts []Extension) (*Client, error) {
       
   105 	// Include the mandatory extensions.
       
   106 	roster := newRosterExt()
       
   107 	exts = append(exts, roster.Extension)
       
   108 	exts = append(exts, bindExt)
       
   109 
       
   110 	// Resolve the domain in the JID.
       
   111 	_, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
       
   112 	if err != nil {
       
   113 		return nil, errors.New("LookupSrv " + jid.Domain +
       
   114 			": " + err.Error())
       
   115 	}
       
   116 
       
   117 	var tcp *net.TCPConn
       
   118 	for _, srv := range srvs {
       
   119 		addrStr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
       
   120 		addr, err := net.ResolveTCPAddr("tcp", addrStr)
       
   121 		if err != nil {
       
   122 			err = fmt.Errorf("ResolveTCPAddr(%s): %s",
       
   123 				addrStr, err.Error())
       
   124 			continue
       
   125 		}
       
   126 		tcp, err = net.DialTCP("tcp", nil, addr)
       
   127 		if err == nil {
       
   128 			break
       
   129 		}
       
   130 		err = fmt.Errorf("DialTCP(%s): %s", addr, err)
       
   131 	}
       
   132 	if tcp == nil {
       
   133 		return nil, err
       
   134 	}
       
   135 
       
   136 	cl := new(Client)
       
   137 	cl.Roster = *roster
       
   138 	cl.password = password
       
   139 	cl.Jid = *jid
       
   140 	cl.socket = tcp
       
   141 	cl.handlers = make(chan *stanzaHandler, 100)
       
   142 	cl.inputControl = make(chan int)
       
   143 
       
   144 	extStanza := make(map[string]func(*xml.Name) interface{})
       
   145 	for _, ext := range exts {
       
   146 		for k, v := range ext.StanzaHandlers {
       
   147 			extStanza[k] = v
       
   148 		}
       
   149 	}
       
   150 
       
   151 	// Start the transport handler, initially unencrypted.
       
   152 	recvReader, recvWriter := io.Pipe()
       
   153 	sendReader, sendWriter := io.Pipe()
       
   154 	go cl.readTransport(recvWriter)
       
   155 	go cl.writeTransport(sendReader)
       
   156 
       
   157 	// Start the reader and writer that convert to and from XML.
       
   158 	recvXml := make(chan interface{})
       
   159 	go readXml(recvReader, recvXml, extStanza)
       
   160 	sendXml := make(chan interface{})
       
   161 	cl.xmlOut = sendXml
       
   162 	go writeXml(sendWriter, sendXml)
       
   163 
       
   164 	// Start the reader and writer that convert between XML and
       
   165 	// XMPP stanzas.
       
   166 	recvRawXmpp := make(chan Stanza)
       
   167 	go cl.readStream(recvXml, recvRawXmpp)
       
   168 	sendRawXmpp := make(chan Stanza)
       
   169 	go writeStream(sendXml, sendRawXmpp, cl.inputControl)
       
   170 
       
   171 	// Start the manager for the filters that can modify what the
       
   172 	// app sees.
       
   173 	recvFiltXmpp := make(chan Stanza)
       
   174 	cl.In = recvFiltXmpp
       
   175 	go filterMgr(cl.recvFilterAdd, recvRawXmpp, recvFiltXmpp)
       
   176 	sendFiltXmpp := make(chan Stanza)
       
   177 	cl.Out = sendFiltXmpp
       
   178 	go filterMgr(cl.sendFilterAdd, sendFiltXmpp, sendFiltXmpp)
       
   179 
       
   180 	// Initial handshake.
       
   181 	hsOut := &stream{To: jid.Domain, Version: XMPPVersion}
       
   182 	cl.xmlOut <- hsOut
       
   183 
       
   184 	return cl, nil
       
   185 }
       
   186 
       
   187 func tee(r io.Reader, w io.Writer, prefix string) {
       
   188 	defer func(w io.Writer) {
       
   189 		if c, ok := w.(io.Closer); ok {
       
   190 			c.Close()
       
   191 		}
       
   192 	}(w)
       
   193 
       
   194 	buf := bytes.NewBuffer([]uint8(prefix))
       
   195 	for {
       
   196 		var c [1]byte
       
   197 		n, _ := r.Read(c[:])
       
   198 		if n == 0 {
       
   199 			break
       
   200 		}
       
   201 		n, _ = w.Write(c[:n])
       
   202 		if n == 0 {
       
   203 			break
       
   204 		}
       
   205 		buf.Write(c[:n])
       
   206 		if c[0] == '\n' || c[0] == '>' {
       
   207 			Debug.Log(buf)
       
   208 			buf = bytes.NewBuffer([]uint8(prefix))
       
   209 		}
       
   210 	}
       
   211 	leftover := buf.String()
       
   212 	if leftover != "" {
       
   213 		Debug.Log(buf)
       
   214 	}
       
   215 }
       
   216 
       
   217 // bindDone is called when we've finished resource binding (and all
       
   218 // the negotiations that precede it). Now we can start accepting
       
   219 // traffic from the app.
       
   220 func (cl *Client) bindDone() {
       
   221 	cl.inputControl <- 1
       
   222 }
       
   223 
       
   224 // Start an XMPP session. A typical XMPP client should call this
       
   225 // immediately after creating the Client in order to start the
       
   226 // session, retrieve the roster, and broadcast an initial
       
   227 // presence. The presence can be as simple as a newly-initialized
       
   228 // Presence struct.  See RFC 3921, Section 3. After calling this, a
       
   229 // normal client will want to call Roster.Update().
       
   230 func (cl *Client) StartSession(pr *Presence) error {
       
   231 	id := NextId()
       
   232 	iq := &Iq{Header: Header{To: cl.Jid.Domain, Id: id, Type: "set",
       
   233 		Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}}
       
   234 	ch := make(chan error)
       
   235 	f := func(st Stanza) bool {
       
   236 		iq, ok := st.(*Iq)
       
   237 		if !ok {
       
   238 			Warn.Log("iq reply not iq; can't start session")
       
   239 			ch <- errors.New("bad session start reply")
       
   240 			return false
       
   241 		}
       
   242 		if iq.Type == "error" {
       
   243 			Warn.Logf("Can't start session: %v", iq)
       
   244 			ch <- iq.Error
       
   245 			return false
       
   246 		}
       
   247 		ch <- nil
       
   248 		return false
       
   249 	}
       
   250 	cl.HandleStanza(id, f)
       
   251 	cl.Out <- iq
       
   252 
       
   253 	// Now wait until the callback is called.
       
   254 	if err := <-ch; err != nil {
       
   255 		return err
       
   256 	}
       
   257 	if pr != nil {
       
   258 		cl.Out <- pr
       
   259 	}
       
   260 	return nil
       
   261 }