Split stream.go into layer1, layer2, layer3, and sasl.
authorChris Jones <christian.jones@sri.com>
Sun, 15 Sep 2013 12:00:17 -0600
changeset 143 62166e57800e
parent 142 0ff033eed887
child 144 9d7fdb1d2fc1
Split stream.go into layer1, layer2, layer3, and sasl.
xmpp/layer1.go
xmpp/layer2.go
xmpp/layer3.go
xmpp/sasl.go
xmpp/sasl_test.go
xmpp/stream.go
xmpp/stream_test.go
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/layer1.go	Sun Sep 15 12:00:17 2013 -0600
@@ -0,0 +1,53 @@
+// The lowest level of XMPP protocol, where TLS is applied after the
+// initial handshake.
+
+package xmpp
+
+import (
+	"io"
+	"time"
+	"net"
+)
+
+func (cl *Client) readTransport(w io.WriteCloser) {
+	defer w.Close()
+	p := make([]byte, 1024)
+	for {
+		if cl.socket == nil {
+			cl.waitForSocket()
+		}
+		cl.socket.SetReadDeadline(time.Now().Add(time.Second))
+		nr, err := cl.socket.Read(p)
+		if nr == 0 {
+			if errno, ok := err.(*net.OpError); ok {
+				if errno.Timeout() {
+					continue
+				}
+			}
+			Warn.Logf("read: %s", err)
+			break
+		}
+		nw, err := w.Write(p[:nr])
+		if nw < nr {
+			Warn.Logf("read: %s", err)
+			break
+		}
+	}
+}
+
+func (cl *Client) writeTransport(r io.Reader) {
+	defer cl.socket.Close()
+	p := make([]byte, 1024)
+	for {
+		nr, err := r.Read(p)
+		if nr == 0 {
+			Warn.Logf("write: %s", err)
+			break
+		}
+		nw, err := cl.socket.Write(p[:nr])
+		if nw < nr {
+			Warn.Logf("write: %s", err)
+			break
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/layer2.go	Sun Sep 15 12:00:17 2013 -0600
@@ -0,0 +1,160 @@
+// This layer of the XMPP protocol translates between bytes and XMLish
+// structures.
+
+package xmpp
+
+import (
+	"io"
+	"reflect"
+	"encoding/xml"
+	"fmt"
+	"strings"
+)
+
+func readXml(r io.Reader, ch chan<- interface{},
+	extStanza map[xml.Name]reflect.Type) {
+	if _, ok := Debug.(*noLog); !ok {
+		pr, pw := io.Pipe()
+		go tee(r, pw, "S: ")
+		r = pr
+	}
+	defer close(ch)
+
+	// This trick loads our namespaces into the parser.
+	nsstr := fmt.Sprintf(`<a xmlns="%s" xmlns:stream="%s">`,
+		NsClient, NsStream)
+	nsrdr := strings.NewReader(nsstr)
+	p := xml.NewDecoder(io.MultiReader(nsrdr, r))
+	p.Token()
+
+Loop:
+	for {
+		// Sniff the next token on the stream.
+		t, err := p.Token()
+		if t == nil {
+			if err != io.EOF {
+				Warn.Logf("read: %s", err)
+			}
+			break
+		}
+		var se xml.StartElement
+		var ok bool
+		if se, ok = t.(xml.StartElement); !ok {
+			continue
+		}
+
+		// Allocate the appropriate structure for this token.
+		var obj interface{}
+		switch se.Name.Space + " " + se.Name.Local {
+		case NsStream + " stream":
+			st, err := parseStream(se)
+			if err != nil {
+				Warn.Logf("unmarshal stream: %s", err)
+				break Loop
+			}
+			ch <- st
+			continue
+		case "stream error", NsStream + " error":
+			obj = &streamError{}
+		case NsStream + " features":
+			obj = &Features{}
+		case NsTLS + " proceed", NsTLS + " failure":
+			obj = &starttls{}
+		case NsSASL + " challenge", NsSASL + " failure",
+			NsSASL + " success":
+			obj = &auth{}
+		case NsClient + " iq":
+			obj = &Iq{}
+		case NsClient + " message":
+			obj = &Message{}
+		case NsClient + " presence":
+			obj = &Presence{}
+		default:
+			obj = &Generic{}
+			Info.Logf("Ignoring unrecognized: %s %s", se.Name.Space,
+				se.Name.Local)
+		}
+
+		// Read the complete XML stanza.
+		err = p.DecodeElement(obj, &se)
+		if err != nil {
+			Warn.Logf("unmarshal: %s", err)
+			break Loop
+		}
+
+		// If it's a Stanza, we try to unmarshal its innerxml
+		// into objects of the appropriate respective
+		// types. This is specified by our extensions.
+		if st, ok := obj.(Stanza); ok {
+			err = parseExtended(st.GetHeader(), extStanza)
+			if err != nil {
+				Warn.Logf("ext unmarshal: %s", err)
+				break Loop
+			}
+		}
+
+		// Put it on the channel.
+		ch <- obj
+	}
+}
+
+func parseExtended(st *Header, extStanza map[xml.Name]reflect.Type) error {
+	// Now parse the stanza's innerxml to find the string that we
+	// can unmarshal this nested element from.
+	reader := strings.NewReader(st.Innerxml)
+	p := xml.NewDecoder(reader)
+	for {
+		t, err := p.Token()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return err
+		}
+		if se, ok := t.(xml.StartElement); ok {
+			if typ, ok := extStanza[se.Name]; ok {
+				nested := reflect.New(typ).Interface()
+
+				// Unmarshal the nested element and
+				// stuff it back into the stanza.
+				err := p.DecodeElement(nested, &se)
+				if err != nil {
+					return err
+				}
+				st.Nested = append(st.Nested, nested)
+			}
+		}
+	}
+
+	return nil
+}
+
+func writeXml(w io.Writer, ch <-chan interface{}) {
+	if _, ok := Debug.(*noLog); !ok {
+		pr, pw := io.Pipe()
+		go tee(pr, w, "C: ")
+		w = pw
+	}
+	defer func(w io.Writer) {
+		if c, ok := w.(io.Closer); ok {
+			c.Close()
+		}
+	}(w)
+
+	enc := xml.NewEncoder(w)
+
+	for obj := range ch {
+		if st, ok := obj.(*stream); ok {
+			_, err := w.Write([]byte(st.String()))
+			if err != nil {
+				Warn.Logf("write: %s", err)
+			}
+		} else {
+			err := enc.Encode(obj)
+			if err != nil {
+				Warn.Logf("marshal: %s", err)
+				break
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/layer3.go	Sun Sep 15 12:00:17 2013 -0600
@@ -0,0 +1,225 @@
+// This layer of the XMPP protocol reads XMLish structures and
+// responds to them. It negotiates TLS and authentication.
+
+package xmpp
+
+import (
+	"encoding/xml"
+	"crypto/tls"
+	"time"
+)
+
+// Callback to handle a stanza with a particular id.
+type stanzaHandler struct {
+	id string
+	// Return true means pass this to the application
+	f func(Stanza) bool
+}
+
+func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- Stanza) {
+	defer close(cliOut)
+
+	handlers := make(map[string]func(Stanza) bool)
+Loop:
+	for {
+		select {
+		case h := <-cl.handlers:
+			handlers[h.id] = h.f
+		case x, ok := <-srvIn:
+			if !ok {
+				break Loop
+			}
+			switch obj := x.(type) {
+			case *stream:
+				handleStream(obj)
+			case *streamError:
+				cl.handleStreamError(obj)
+			case *Features:
+				cl.handleFeatures(obj)
+			case *starttls:
+				cl.handleTls(obj)
+			case *auth:
+				cl.handleSasl(obj)
+			case Stanza:
+				send := true
+				id := obj.GetHeader().Id
+				if handlers[id] != nil {
+					f := handlers[id]
+					delete(handlers, id)
+					send = f(obj)
+				}
+				if send {
+					cliOut <- obj
+				}
+			default:
+				Warn.Logf("Unhandled non-stanza: %T %#v", x, x)
+			}
+		}
+	}
+}
+
+// This loop is paused until resource binding is complete. Otherwise
+// the app might inject something inappropriate into our negotiations
+// with the server. The control channel controls this loop's
+// activity.
+func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza,
+	control <-chan int) {
+	defer close(srvOut)
+
+	var input <-chan Stanza
+Loop:
+	for {
+		select {
+		case status := <-control:
+			switch status {
+			case 0:
+				input = nil
+			case 1:
+				input = cliIn
+			case -1:
+				break Loop
+			}
+		case x, ok := <-input:
+			if !ok {
+				break Loop
+			}
+			if x == nil {
+				Info.Log("Refusing to send nil stanza")
+				continue
+			}
+			srvOut <- x
+		}
+	}
+}
+
+func handleStream(ss *stream) {
+}
+
+func (cl *Client) handleStreamError(se *streamError) {
+	Info.Logf("Received stream error: %v", se)
+	cl.socket.Close()
+}
+
+func (cl *Client) handleFeatures(fe *Features) {
+	cl.Features = fe
+	if fe.Starttls != nil {
+		start := &starttls{XMLName: xml.Name{Space: NsTLS,
+			Local: "starttls"}}
+		cl.sendXml <- start
+		return
+	}
+
+	if len(fe.Mechanisms.Mechanism) > 0 {
+		cl.chooseSasl(fe)
+		return
+	}
+
+	if fe.Bind != nil {
+		cl.bind(fe.Bind)
+		return
+	}
+}
+
+// readTransport() is running concurrently. We need to stop it,
+// negotiate TLS, then start it again. It calls waitForSocket() in
+// its inner loop; see below.
+func (cl *Client) handleTls(t *starttls) {
+	tcp := cl.socket
+
+	// Set the socket to nil, and wait for the reader routine to
+	// signal that it's paused.
+	cl.socket = nil
+	cl.socketSync.Add(1)
+	cl.socketSync.Wait()
+
+	// Negotiate TLS with the server.
+	tls := tls.Client(tcp, &cl.tlsConfig)
+
+	// Make the TLS connection available to the reader, and wait
+	// for it to signal that it's working again.
+	cl.socketSync.Add(1)
+	cl.socket = tls
+	cl.socketSync.Wait()
+
+	Info.Log("TLS negotiation succeeded.")
+	cl.Features = nil
+
+	// Now re-send the initial handshake message to start the new
+	// session.
+	hsOut := &stream{To: cl.Jid.Domain, Version: XMPPVersion}
+	cl.sendXml <- hsOut
+}
+
+// Synchronize with handleTls(). Called from readTransport() when
+// cl.socket is nil.
+func (cl *Client) waitForSocket() {
+	// Signal that we've stopped reading from the socket.
+	cl.socketSync.Done()
+
+	// Wait until the socket is available again.
+	for cl.socket == nil {
+		time.Sleep(1e8)
+	}
+
+	// Signal that we're going back to the read loop.
+	cl.socketSync.Done()
+}
+
+// Register a callback to handle the next XMPP stanza (iq, message, or
+// presence) with a given id. The provided function will not be called
+// more than once. If it returns false, the stanza will not be made
+// available on the normal Client.In channel. The stanza handler
+// must not read from that channel, as deliveries on it cannot proceed
+// until the handler returns true or false.
+func (cl *Client) HandleStanza(id string, f func(Stanza) bool) {
+	h := &stanzaHandler{id: id, f: f}
+	cl.handlers <- h
+}
+
+// Send a request to bind a resource. RFC 3920, section 7.
+func (cl *Client) bind(bindAdv *bindIq) {
+	res := cl.Jid.Resource
+	bindReq := &bindIq{}
+	if res != "" {
+		bindReq.Resource = &res
+	}
+	msg := &Iq{Header: Header{Type: "set", Id: NextId(),
+		Nested: []interface{}{bindReq}}}
+	f := func(st Stanza) bool {
+		iq, ok := st.(*Iq)
+		if !ok {
+			Warn.Log("non-iq response")
+		}
+		if iq.Type == "error" {
+			Warn.Log("Resource binding failed")
+			return false
+		}
+		var bindRepl *bindIq
+		for _, ele := range iq.Nested {
+			if b, ok := ele.(*bindIq); ok {
+				bindRepl = b
+				break
+			}
+		}
+		if bindRepl == nil {
+			Warn.Logf("Bad bind reply: %#v", iq)
+			return false
+		}
+		jidStr := bindRepl.Jid
+		if jidStr == nil || *jidStr == "" {
+			Warn.Log("Can't bind empty resource")
+			return false
+		}
+		jid := new(JID)
+		if err := jid.Set(*jidStr); err != nil {
+			Warn.Logf("Can't parse JID %s: %s", *jidStr, err)
+			return false
+		}
+		cl.Jid = *jid
+		Info.Logf("Bound resource: %s", cl.Jid.String())
+		cl.bindDone()
+		return false
+	}
+	cl.HandleStanza(msg.Id, f)
+	cl.sendXml <- msg
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/sasl.go	Sun Sep 15 12:00:17 2013 -0600
@@ -0,0 +1,190 @@
+// Deal with SASL authentication.
+
+package xmpp
+
+import (
+	"strings"
+	"encoding/xml"
+	"encoding/base64"
+	"fmt"
+	"math/big"
+	"crypto/rand"
+	"regexp"
+	"crypto/md5"
+)
+
+// BUG(cjyar): Doesn't implement TLS/SASL EXTERNAL.
+func (cl *Client) chooseSasl(fe *Features) {
+	var digestMd5 bool
+	for _, m := range fe.Mechanisms.Mechanism {
+		switch strings.ToLower(m) {
+		case "digest-md5":
+			digestMd5 = true
+		}
+	}
+
+	if digestMd5 {
+		auth := &auth{XMLName: xml.Name{Space: NsSASL, Local: "auth"}, Mechanism: "DIGEST-MD5"}
+		cl.sendXml <- auth
+	}
+}
+
+func (cl *Client) handleSasl(srv *auth) {
+	switch strings.ToLower(srv.XMLName.Local) {
+	case "challenge":
+		b64 := base64.StdEncoding
+		str, err := b64.DecodeString(srv.Chardata)
+		if err != nil {
+			Warn.Logf("SASL challenge decode: %s", err)
+			return
+		}
+		srvMap := parseSasl(string(str))
+
+		if cl.saslExpected == "" {
+			cl.saslDigest1(srvMap)
+		} else {
+			cl.saslDigest2(srvMap)
+		}
+	case "failure":
+		Info.Log("SASL authentication failed")
+	case "success":
+		Info.Log("Sasl authentication succeeded")
+		cl.Features = nil
+		ss := &stream{To: cl.Jid.Domain, Version: XMPPVersion}
+		cl.sendXml <- ss
+	}
+}
+
+func (cl *Client) saslDigest1(srvMap map[string]string) {
+	// Make sure it supports qop=auth
+	var hasAuth bool
+	for _, qop := range strings.Fields(srvMap["qop"]) {
+		if qop == "auth" {
+			hasAuth = true
+		}
+	}
+	if !hasAuth {
+		Warn.Log("Server doesn't support SASL auth")
+		return
+	}
+
+	// Pick a realm.
+	var realm string
+	if srvMap["realm"] != "" {
+		realm = strings.Fields(srvMap["realm"])[0]
+	}
+
+	passwd := cl.password
+	nonce := srvMap["nonce"]
+	digestUri := "xmpp/" + cl.Jid.Domain
+	nonceCount := int32(1)
+	nonceCountStr := fmt.Sprintf("%08x", nonceCount)
+
+	// Begin building the response. Username is
+	// user@domain or just domain.
+	var username string
+	if cl.Jid.Node == "" {
+		username = cl.Jid.Domain
+	} else {
+		username = cl.Jid.Node
+	}
+
+	// Generate our own nonce from random data.
+	randSize := big.NewInt(0)
+	randSize.Lsh(big.NewInt(1), 64)
+	cnonce, err := rand.Int(rand.Reader, randSize)
+	if err != nil {
+		Warn.Logf("SASL rand: %s", err)
+		return
+	}
+	cnonceStr := fmt.Sprintf("%016x", cnonce)
+
+	/* Now encode the actual password response, as well as the
+	 * expected next challenge from the server. */
+	response := saslDigestResponse(username, realm, passwd, nonce,
+		cnonceStr, "AUTHENTICATE", digestUri, nonceCountStr)
+	next := saslDigestResponse(username, realm, passwd, nonce,
+		cnonceStr, "", digestUri, nonceCountStr)
+	cl.saslExpected = next
+
+	// Build the map which will be encoded.
+	clMap := make(map[string]string)
+	clMap["realm"] = `"` + realm + `"`
+	clMap["username"] = `"` + username + `"`
+	clMap["nonce"] = `"` + nonce + `"`
+	clMap["cnonce"] = `"` + cnonceStr + `"`
+	clMap["nc"] = nonceCountStr
+	clMap["qop"] = "auth"
+	clMap["digest-uri"] = `"` + digestUri + `"`
+	clMap["response"] = response
+	if srvMap["charset"] == "utf-8" {
+		clMap["charset"] = "utf-8"
+	}
+
+	// Encode the map and send it.
+	clStr := packSasl(clMap)
+	b64 := base64.StdEncoding
+	clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "response"}, Chardata: b64.EncodeToString([]byte(clStr))}
+	cl.sendXml <- clObj
+}
+
+func (cl *Client) saslDigest2(srvMap map[string]string) {
+	if cl.saslExpected == srvMap["rspauth"] {
+		clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "response"}}
+		cl.sendXml <- clObj
+	} else {
+		clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "failure"}, Any: &Generic{XMLName: xml.Name{Space: NsSASL,
+			Local: "abort"}}}
+		cl.sendXml <- clObj
+	}
+}
+
+// Takes a string like `key1=value1,key2="value2"...` and returns a
+// key/value map.
+func parseSasl(in string) map[string]string {
+	re := regexp.MustCompile(`([^=]+)="?([^",]+)"?,?`)
+	strs := re.FindAllStringSubmatch(in, -1)
+	m := make(map[string]string)
+	for _, pair := range strs {
+		key := strings.ToLower(string(pair[1]))
+		value := string(pair[2])
+		m[key] = value
+	}
+	return m
+}
+
+// Inverse of parseSasl().
+func packSasl(m map[string]string) string {
+	var terms []string
+	for key, value := range m {
+		if key == "" || value == "" || value == `""` {
+			continue
+		}
+		terms = append(terms, key+"="+value)
+	}
+	return strings.Join(terms, ",")
+}
+
+// Computes the response string for digest authentication.
+func saslDigestResponse(username, realm, passwd, nonce, cnonceStr,
+	authenticate, digestUri, nonceCountStr string) string {
+	h := func(text string) []byte {
+		h := md5.New()
+		h.Write([]byte(text))
+		return h.Sum(nil)
+	}
+	hex := func(bytes []byte) string {
+		return fmt.Sprintf("%x", bytes)
+	}
+	kd := func(secret, data string) []byte {
+		return h(secret + ":" + data)
+	}
+
+	a1 := string(h(username+":"+realm+":"+passwd)) + ":" +
+		nonce + ":" + cnonceStr
+	a2 := authenticate + ":" + digestUri
+	response := hex(kd(hex(h(a1)), nonce+":"+
+		nonceCountStr+":"+cnonceStr+":auth:"+
+		hex(h(a2))))
+	return response
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/sasl_test.go	Sun Sep 15 12:00:17 2013 -0600
@@ -0,0 +1,15 @@
+package xmpp
+
+import (
+	"testing"
+)
+
+func TestSaslDigest(t *testing.T) {
+	// These values are from RFC2831, section 4.
+	obs := saslDigestResponse("chris", "elwood.innosoft.com",
+		"secret", "OA6MG9tEQGm2hh", "OA6MHXh6VqTrRk",
+		"AUTHENTICATE", "imap/elwood.innosoft.com",
+		"00000001")
+	exp := "d388dad90d4bbd760a152321f2143af7"
+	assertEquals(t, exp, obs)
+}
--- a/xmpp/stream.go	Sun Sep 15 11:34:35 2013 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,605 +0,0 @@
-// This file contains the three layers of processing for the
-// communication with the server: transport (where TLS happens), XML
-// (where strings are converted to go structures), and Stream (where
-// we respond to XMPP events on behalf of the library client), or send
-// those events to the client.
-
-package xmpp
-
-import (
-	"crypto/md5"
-	"crypto/rand"
-	"crypto/tls"
-	"encoding/base64"
-	"encoding/xml"
-	"fmt"
-	"io"
-	"math/big"
-	"net"
-	"reflect"
-	"regexp"
-	"strings"
-	"time"
-)
-
-// Callback to handle a stanza with a particular id.
-type stanzaHandler struct {
-	id string
-	// Return true means pass this to the application
-	f func(Stanza) bool
-}
-
-func (cl *Client) readTransport(w io.WriteCloser) {
-	defer w.Close()
-	p := make([]byte, 1024)
-	for {
-		if cl.socket == nil {
-			cl.waitForSocket()
-		}
-		cl.socket.SetReadDeadline(time.Now().Add(time.Second))
-		nr, err := cl.socket.Read(p)
-		if nr == 0 {
-			if errno, ok := err.(*net.OpError); ok {
-				if errno.Timeout() {
-					continue
-				}
-			}
-			Warn.Logf("read: %s", err)
-			break
-		}
-		nw, err := w.Write(p[:nr])
-		if nw < nr {
-			Warn.Logf("read: %s", err)
-			break
-		}
-	}
-}
-
-func (cl *Client) writeTransport(r io.Reader) {
-	defer cl.socket.Close()
-	p := make([]byte, 1024)
-	for {
-		nr, err := r.Read(p)
-		if nr == 0 {
-			Warn.Logf("write: %s", err)
-			break
-		}
-		nw, err := cl.socket.Write(p[:nr])
-		if nw < nr {
-			Warn.Logf("write: %s", err)
-			break
-		}
-	}
-}
-
-func readXml(r io.Reader, ch chan<- interface{},
-	extStanza map[xml.Name]reflect.Type) {
-	if _, ok := Debug.(*noLog); !ok {
-		pr, pw := io.Pipe()
-		go tee(r, pw, "S: ")
-		r = pr
-	}
-	defer close(ch)
-
-	// This trick loads our namespaces into the parser.
-	nsstr := fmt.Sprintf(`<a xmlns="%s" xmlns:stream="%s">`,
-		NsClient, NsStream)
-	nsrdr := strings.NewReader(nsstr)
-	p := xml.NewDecoder(io.MultiReader(nsrdr, r))
-	p.Token()
-
-Loop:
-	for {
-		// Sniff the next token on the stream.
-		t, err := p.Token()
-		if t == nil {
-			if err != io.EOF {
-				Warn.Logf("read: %s", err)
-			}
-			break
-		}
-		var se xml.StartElement
-		var ok bool
-		if se, ok = t.(xml.StartElement); !ok {
-			continue
-		}
-
-		// Allocate the appropriate structure for this token.
-		var obj interface{}
-		switch se.Name.Space + " " + se.Name.Local {
-		case NsStream + " stream":
-			st, err := parseStream(se)
-			if err != nil {
-				Warn.Logf("unmarshal stream: %s", err)
-				break Loop
-			}
-			ch <- st
-			continue
-		case "stream error", NsStream + " error":
-			obj = &streamError{}
-		case NsStream + " features":
-			obj = &Features{}
-		case NsTLS + " proceed", NsTLS + " failure":
-			obj = &starttls{}
-		case NsSASL + " challenge", NsSASL + " failure",
-			NsSASL + " success":
-			obj = &auth{}
-		case NsClient + " iq":
-			obj = &Iq{}
-		case NsClient + " message":
-			obj = &Message{}
-		case NsClient + " presence":
-			obj = &Presence{}
-		default:
-			obj = &Generic{}
-			Info.Logf("Ignoring unrecognized: %s %s", se.Name.Space,
-				se.Name.Local)
-		}
-
-		// Read the complete XML stanza.
-		err = p.DecodeElement(obj, &se)
-		if err != nil {
-			Warn.Logf("unmarshal: %s", err)
-			break Loop
-		}
-
-		// If it's a Stanza, we try to unmarshal its innerxml
-		// into objects of the appropriate respective
-		// types. This is specified by our extensions.
-		if st, ok := obj.(Stanza); ok {
-			err = parseExtended(st.GetHeader(), extStanza)
-			if err != nil {
-				Warn.Logf("ext unmarshal: %s", err)
-				break Loop
-			}
-		}
-
-		// Put it on the channel.
-		ch <- obj
-	}
-}
-
-func parseExtended(st *Header, extStanza map[xml.Name]reflect.Type) error {
-	// Now parse the stanza's innerxml to find the string that we
-	// can unmarshal this nested element from.
-	reader := strings.NewReader(st.Innerxml)
-	p := xml.NewDecoder(reader)
-	for {
-		t, err := p.Token()
-		if err == io.EOF {
-			break
-		}
-		if err != nil {
-			return err
-		}
-		if se, ok := t.(xml.StartElement); ok {
-			if typ, ok := extStanza[se.Name]; ok {
-				nested := reflect.New(typ).Interface()
-
-				// Unmarshal the nested element and
-				// stuff it back into the stanza.
-				err := p.DecodeElement(nested, &se)
-				if err != nil {
-					return err
-				}
-				st.Nested = append(st.Nested, nested)
-			}
-		}
-	}
-
-	return nil
-}
-
-func writeXml(w io.Writer, ch <-chan interface{}) {
-	if _, ok := Debug.(*noLog); !ok {
-		pr, pw := io.Pipe()
-		go tee(pr, w, "C: ")
-		w = pw
-	}
-	defer func(w io.Writer) {
-		if c, ok := w.(io.Closer); ok {
-			c.Close()
-		}
-	}(w)
-
-	enc := xml.NewEncoder(w)
-
-	for obj := range ch {
-		if st, ok := obj.(*stream); ok {
-			_, err := w.Write([]byte(st.String()))
-			if err != nil {
-				Warn.Logf("write: %s", err)
-			}
-		} else {
-			err := enc.Encode(obj)
-			if err != nil {
-				Warn.Logf("marshal: %s", err)
-				break
-			}
-		}
-	}
-}
-
-func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- Stanza) {
-	defer close(cliOut)
-
-	handlers := make(map[string]func(Stanza) bool)
-Loop:
-	for {
-		select {
-		case h := <-cl.handlers:
-			handlers[h.id] = h.f
-		case x, ok := <-srvIn:
-			if !ok {
-				break Loop
-			}
-			switch obj := x.(type) {
-			case *stream:
-				handleStream(obj)
-			case *streamError:
-				cl.handleStreamError(obj)
-			case *Features:
-				cl.handleFeatures(obj)
-			case *starttls:
-				cl.handleTls(obj)
-			case *auth:
-				cl.handleSasl(obj)
-			case Stanza:
-				send := true
-				id := obj.GetHeader().Id
-				if handlers[id] != nil {
-					f := handlers[id]
-					delete(handlers, id)
-					send = f(obj)
-				}
-				if send {
-					cliOut <- obj
-				}
-			default:
-				Warn.Logf("Unhandled non-stanza: %T %#v", x, x)
-			}
-		}
-	}
-}
-
-// This loop is paused until resource binding is complete. Otherwise
-// the app might inject something inappropriate into our negotiations
-// with the server. The control channel controls this loop's
-// activity.
-func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza,
-	control <-chan int) {
-	defer close(srvOut)
-
-	var input <-chan Stanza
-Loop:
-	for {
-		select {
-		case status := <-control:
-			switch status {
-			case 0:
-				input = nil
-			case 1:
-				input = cliIn
-			case -1:
-				break Loop
-			}
-		case x, ok := <-input:
-			if !ok {
-				break Loop
-			}
-			if x == nil {
-				Info.Log("Refusing to send nil stanza")
-				continue
-			}
-			srvOut <- x
-		}
-	}
-}
-
-func handleStream(ss *stream) {
-}
-
-func (cl *Client) handleStreamError(se *streamError) {
-	Info.Logf("Received stream error: %v", se)
-	cl.socket.Close()
-}
-
-func (cl *Client) handleFeatures(fe *Features) {
-	cl.Features = fe
-	if fe.Starttls != nil {
-		start := &starttls{XMLName: xml.Name{Space: NsTLS,
-			Local: "starttls"}}
-		cl.sendXml <- start
-		return
-	}
-
-	if len(fe.Mechanisms.Mechanism) > 0 {
-		cl.chooseSasl(fe)
-		return
-	}
-
-	if fe.Bind != nil {
-		cl.bind(fe.Bind)
-		return
-	}
-}
-
-// readTransport() is running concurrently. We need to stop it,
-// negotiate TLS, then start it again. It calls waitForSocket() in
-// its inner loop; see below.
-func (cl *Client) handleTls(t *starttls) {
-	tcp := cl.socket
-
-	// Set the socket to nil, and wait for the reader routine to
-	// signal that it's paused.
-	cl.socket = nil
-	cl.socketSync.Add(1)
-	cl.socketSync.Wait()
-
-	// Negotiate TLS with the server.
-	tls := tls.Client(tcp, &cl.tlsConfig)
-
-	// Make the TLS connection available to the reader, and wait
-	// for it to signal that it's working again.
-	cl.socketSync.Add(1)
-	cl.socket = tls
-	cl.socketSync.Wait()
-
-	Info.Log("TLS negotiation succeeded.")
-	cl.Features = nil
-
-	// Now re-send the initial handshake message to start the new
-	// session.
-	hsOut := &stream{To: cl.Jid.Domain, Version: XMPPVersion}
-	cl.sendXml <- hsOut
-}
-
-// Synchronize with handleTls(). Called from readTransport() when
-// cl.socket is nil.
-func (cl *Client) waitForSocket() {
-	// Signal that we've stopped reading from the socket.
-	cl.socketSync.Done()
-
-	// Wait until the socket is available again.
-	for cl.socket == nil {
-		time.Sleep(1e8)
-	}
-
-	// Signal that we're going back to the read loop.
-	cl.socketSync.Done()
-}
-
-// BUG(cjyar): Doesn't implement TLS/SASL EXTERNAL.
-func (cl *Client) chooseSasl(fe *Features) {
-	var digestMd5 bool
-	for _, m := range fe.Mechanisms.Mechanism {
-		switch strings.ToLower(m) {
-		case "digest-md5":
-			digestMd5 = true
-		}
-	}
-
-	if digestMd5 {
-		auth := &auth{XMLName: xml.Name{Space: NsSASL, Local: "auth"}, Mechanism: "DIGEST-MD5"}
-		cl.sendXml <- auth
-	}
-}
-
-func (cl *Client) handleSasl(srv *auth) {
-	switch strings.ToLower(srv.XMLName.Local) {
-	case "challenge":
-		b64 := base64.StdEncoding
-		str, err := b64.DecodeString(srv.Chardata)
-		if err != nil {
-			Warn.Logf("SASL challenge decode: %s", err)
-			return
-		}
-		srvMap := parseSasl(string(str))
-
-		if cl.saslExpected == "" {
-			cl.saslDigest1(srvMap)
-		} else {
-			cl.saslDigest2(srvMap)
-		}
-	case "failure":
-		Info.Log("SASL authentication failed")
-	case "success":
-		Info.Log("Sasl authentication succeeded")
-		cl.Features = nil
-		ss := &stream{To: cl.Jid.Domain, Version: XMPPVersion}
-		cl.sendXml <- ss
-	}
-}
-
-func (cl *Client) saslDigest1(srvMap map[string]string) {
-	// Make sure it supports qop=auth
-	var hasAuth bool
-	for _, qop := range strings.Fields(srvMap["qop"]) {
-		if qop == "auth" {
-			hasAuth = true
-		}
-	}
-	if !hasAuth {
-		Warn.Log("Server doesn't support SASL auth")
-		return
-	}
-
-	// Pick a realm.
-	var realm string
-	if srvMap["realm"] != "" {
-		realm = strings.Fields(srvMap["realm"])[0]
-	}
-
-	passwd := cl.password
-	nonce := srvMap["nonce"]
-	digestUri := "xmpp/" + cl.Jid.Domain
-	nonceCount := int32(1)
-	nonceCountStr := fmt.Sprintf("%08x", nonceCount)
-
-	// Begin building the response. Username is
-	// user@domain or just domain.
-	var username string
-	if cl.Jid.Node == "" {
-		username = cl.Jid.Domain
-	} else {
-		username = cl.Jid.Node
-	}
-
-	// Generate our own nonce from random data.
-	randSize := big.NewInt(0)
-	randSize.Lsh(big.NewInt(1), 64)
-	cnonce, err := rand.Int(rand.Reader, randSize)
-	if err != nil {
-		Warn.Logf("SASL rand: %s", err)
-		return
-	}
-	cnonceStr := fmt.Sprintf("%016x", cnonce)
-
-	/* Now encode the actual password response, as well as the
-	 * expected next challenge from the server. */
-	response := saslDigestResponse(username, realm, passwd, nonce,
-		cnonceStr, "AUTHENTICATE", digestUri, nonceCountStr)
-	next := saslDigestResponse(username, realm, passwd, nonce,
-		cnonceStr, "", digestUri, nonceCountStr)
-	cl.saslExpected = next
-
-	// Build the map which will be encoded.
-	clMap := make(map[string]string)
-	clMap["realm"] = `"` + realm + `"`
-	clMap["username"] = `"` + username + `"`
-	clMap["nonce"] = `"` + nonce + `"`
-	clMap["cnonce"] = `"` + cnonceStr + `"`
-	clMap["nc"] = nonceCountStr
-	clMap["qop"] = "auth"
-	clMap["digest-uri"] = `"` + digestUri + `"`
-	clMap["response"] = response
-	if srvMap["charset"] == "utf-8" {
-		clMap["charset"] = "utf-8"
-	}
-
-	// Encode the map and send it.
-	clStr := packSasl(clMap)
-	b64 := base64.StdEncoding
-	clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "response"}, Chardata: b64.EncodeToString([]byte(clStr))}
-	cl.sendXml <- clObj
-}
-
-func (cl *Client) saslDigest2(srvMap map[string]string) {
-	if cl.saslExpected == srvMap["rspauth"] {
-		clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "response"}}
-		cl.sendXml <- clObj
-	} else {
-		clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "failure"}, Any: &Generic{XMLName: xml.Name{Space: NsSASL,
-			Local: "abort"}}}
-		cl.sendXml <- clObj
-	}
-}
-
-// Takes a string like `key1=value1,key2="value2"...` and returns a
-// key/value map.
-func parseSasl(in string) map[string]string {
-	re := regexp.MustCompile(`([^=]+)="?([^",]+)"?,?`)
-	strs := re.FindAllStringSubmatch(in, -1)
-	m := make(map[string]string)
-	for _, pair := range strs {
-		key := strings.ToLower(string(pair[1]))
-		value := string(pair[2])
-		m[key] = value
-	}
-	return m
-}
-
-// Inverse of parseSasl().
-func packSasl(m map[string]string) string {
-	var terms []string
-	for key, value := range m {
-		if key == "" || value == "" || value == `""` {
-			continue
-		}
-		terms = append(terms, key+"="+value)
-	}
-	return strings.Join(terms, ",")
-}
-
-// Computes the response string for digest authentication.
-func saslDigestResponse(username, realm, passwd, nonce, cnonceStr,
-	authenticate, digestUri, nonceCountStr string) string {
-	h := func(text string) []byte {
-		h := md5.New()
-		h.Write([]byte(text))
-		return h.Sum(nil)
-	}
-	hex := func(bytes []byte) string {
-		return fmt.Sprintf("%x", bytes)
-	}
-	kd := func(secret, data string) []byte {
-		return h(secret + ":" + data)
-	}
-
-	a1 := string(h(username+":"+realm+":"+passwd)) + ":" +
-		nonce + ":" + cnonceStr
-	a2 := authenticate + ":" + digestUri
-	response := hex(kd(hex(h(a1)), nonce+":"+
-		nonceCountStr+":"+cnonceStr+":auth:"+
-		hex(h(a2))))
-	return response
-}
-
-// Send a request to bind a resource. RFC 3920, section 7.
-func (cl *Client) bind(bindAdv *bindIq) {
-	res := cl.Jid.Resource
-	bindReq := &bindIq{}
-	if res != "" {
-		bindReq.Resource = &res
-	}
-	msg := &Iq{Header: Header{Type: "set", Id: NextId(),
-		Nested: []interface{}{bindReq}}}
-	f := func(st Stanza) bool {
-		iq, ok := st.(*Iq)
-		if !ok {
-			Warn.Log("non-iq response")
-		}
-		if iq.Type == "error" {
-			Warn.Log("Resource binding failed")
-			return false
-		}
-		var bindRepl *bindIq
-		for _, ele := range iq.Nested {
-			if b, ok := ele.(*bindIq); ok {
-				bindRepl = b
-				break
-			}
-		}
-		if bindRepl == nil {
-			Warn.Logf("Bad bind reply: %#v", iq)
-			return false
-		}
-		jidStr := bindRepl.Jid
-		if jidStr == nil || *jidStr == "" {
-			Warn.Log("Can't bind empty resource")
-			return false
-		}
-		jid := new(JID)
-		if err := jid.Set(*jidStr); err != nil {
-			Warn.Logf("Can't parse JID %s: %s", *jidStr, err)
-			return false
-		}
-		cl.Jid = *jid
-		Info.Logf("Bound resource: %s", cl.Jid.String())
-		cl.bindDone()
-		return false
-	}
-	cl.HandleStanza(msg.Id, f)
-	cl.sendXml <- msg
-}
-
-// Register a callback to handle the next XMPP stanza (iq, message, or
-// presence) with a given id. The provided function will not be called
-// more than once. If it returns false, the stanza will not be made
-// available on the normal Client.In channel. The stanza handler
-// must not read from that channel, as deliveries on it cannot proceed
-// until the handler returns true or false.
-func (cl *Client) HandleStanza(id string, f func(Stanza) bool) {
-	h := &stanzaHandler{id: id, f: f}
-	cl.handlers <- h
-}
--- a/xmpp/stream_test.go	Sun Sep 15 11:34:35 2013 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-package xmpp
-
-import (
-	"testing"
-)
-
-func TestSaslDigest(t *testing.T) {
-	// These values are from RFC2831, section 4.
-	obs := saslDigestResponse("chris", "elwood.innosoft.com",
-		"secret", "OA6MG9tEQGm2hh", "OA6MHXh6VqTrRk",
-		"AUTHENTICATE", "imap/elwood.innosoft.com",
-		"00000001")
-	exp := "d388dad90d4bbd760a152321f2143af7"
-	assertEquals(t, exp, obs)
-}