|
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 } |