1package xmpp
2
3import (
4	"bytes"
5	"crypto/tls"
6	"crypto/x509"
7	"encoding/xml"
8	"fmt"
9	"io"
10
11	goerr "errors"
12
13	"github.com/coyim/coyim/digests"
14	"github.com/coyim/coyim/xmpp/data"
15	"github.com/coyim/coyim/xmpp/errors"
16
17	. "gopkg.in/check.v1"
18)
19
20type ConnectionXMPPSuite struct{}
21
22var _ = Suite(&ConnectionXMPPSuite{})
23
24func tlsConfigForRecordedHandshake() (*tls.Config, *basicTLSVerifier) {
25	//This is the certificate in the recorded handshake
26	peerCertificatePEM := []byte(`
27-----BEGIN CERTIFICATE-----
28MIIF5TCCA82gAwIBAgIQJkO7MqFmSHrhnWx5xD/iZjANBgkqhkiG9w0BAQsFADB9
29MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
30U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
31cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTUxMjE2MDEwMDA1WhcN
32MzAxMjE2MDEwMDA1WjB4MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20g
33THRkLjEpMCcGA1UECxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
34JjAkBgNVBAMTHVN0YXJ0Q29tIENsYXNzIDIgSVYgU2VydmVyIENBMIIBIjANBgkq
35hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnL29gjx6E467y4OsHo42TCn1rC7JXUnv
36epzPE9KLbJiQi63JSLTr/QVGjhWFQBhqwXKlyTyBNGoOuV+yRoimqkPDdV6ZdnIn
37RwmKAnVhvMVd2WXeqSJtq5STa2nuOnLTwYBnyVsOIo9YdnvFhDXAGjQ3hXWQIq00
38f43XE8Fik+9EUG/oF7VLlIACAJnhotAj2dR2TvQmyBbEEN2PhLH3WANZklMbao2c
39sASqSwyOmAB5+35nSagpMYuuVa4ZSnm2EaF8emLxiiFK5InCBZjRG4u+YLrEv7+m
40KrnHOMVWkOE7mzKxtuHFYW2LRB++eJGLUdn1KiviZDS/ofOhIhfstwIDAQABo4IB
41ZDCCAWAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
42BQcDATASBgNVHRMBAf8ECDAGAQH/AgEAMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHA6
43Ly9jcmwuc3RhcnRzc2wuY29tL3Nmc2NhLmNybDBmBggrBgEFBQcBAQRaMFgwJAYI
44KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbTAwBggrBgEFBQcwAoYk
45aHR0cDovL2FpYS5zdGFydHNzbC5jb20vY2VydHMvY2EuY3J0MB0GA1UdDgQWBBSU
463oVBKqXZRfZgLC5MkwmmLCN+PjAfBgNVHSMEGDAWgBROC+8apEBbpRdphzDKNGhD
470EGu8jA/BgNVHSAEODA2MDQGBFUdIAAwLDAqBggrBgEFBQcCARYeaHR0cDovL3d3
48dy5zdGFydHNzbC5jb20vcG9saWN5MA0GCSqGSIb3DQEBCwUAA4ICAQC16kMuZh8h
49lVsgzybaIix2qySQFU+rPgqSqeyrDSmJwpDbaKjwakm6LJ2DLX5MRFjNPCh+ArQf
50CU1UUJa65n7UaQWt6q8kUwifHcIn+fFJdNV3N4zdvlKxwveqBSQZiXeIUO/hHr1U
51i7Gw6s0On+K0fD9oNcgCRR3vPicB2frK7BhOFje6xowsWexxPfJHI69lCq73O7Ke
52xXqp/V8f8uGF8L4KU3xW6RDG57RrXh5+LNxUQmZ2tIAaPyHTND5zbxff8Z/ZbgGG
53HKbsuPkAUIG+bHpq5b6bf2x2NxMhqYSMI+GJJ9FmmiCV+P3+0ywBYGNhJkcFUYvo
54SUduHz+/RXd6G/ejrvKp58rbZ9iCISLZjpo5gYEfLIl6IQJcZPM8FIWKLKhtIoKX
555ctNL3epV4DzIDZxLaSruEBQFeDQj6p/74pUYLQBP523anf6StXBtYgbfImRoIh4
56I8L85aB/TUyLOJA/sKx/WFrXOxE9K4q+Pf5tq3gzZEchM/btMYn1cw1GPUt4nHya
57zS52LrP0+Q77ao1Gza9svd8HE1NZ9NIVJO71QskqjxvGiTt048r4gLSXaM1zP2w9
58nMsIw1IpxXE8h9UHAllgh8oNHno5I9nLfynbEhXxGy9RlfcLN/J8iOqyagfgxrUy
59DPKMh5xGeLKMQSzjyQ1bV0WGC1JmJp+QDQ==
60-----END CERTIFICATE-----
61	`)
62
63	pool := x509.NewCertPool()
64	if !pool.AppendCertsFromPEM(peerCertificatePEM) {
65		panic("Bad PEM")
66	}
67
68	c := &tls.Config{
69		RootCAs: pool,
70		CipherSuites: []uint16{
71			0xc02f, 0xc02b, 0xc030, 0xc02c, 0xc013, 0xc009, 0xc014,
72			0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a,
73		},
74		//0x1d appears as preffered since go1.8
75		//Manually inform what was used when the handshake for 1.6 was recorded
76		CurvePreferences: []tls.CurveID{0x17, 0x18, 0x19},
77	}
78
79	v := &basicTLSVerifier{
80		shaSum: []byte{
81			0x82, 0x45, 0x44, 0x18, 0xcb, 0x04, 0x85, 0x4a,
82			0xa7, 0x21, 0xbb, 0x05, 0x96, 0x52, 0x8f, 0xf8,
83			0x02, 0xb1, 0xe1, 0x8a, 0x4e, 0x3a, 0x77, 0x67,
84			0x41, 0x2a, 0xc9, 0xf1, 0x08, 0xc9, 0xd3, 0xa7,
85		},
86	}
87
88	return c, v
89}
90
91type basicTLSVerifier struct {
92	shaSum []byte
93}
94
95func (v *basicTLSVerifier) verifyCert(state tls.ConnectionState, conf *tls.Config) ([][]*x509.Certificate, error) {
96	opts := x509.VerifyOptions{
97		Intermediates: x509.NewCertPool(),
98		Roots:         conf.RootCAs,
99	}
100
101	for _, cert := range state.PeerCertificates[1:] {
102		opts.Intermediates.AddCert(cert)
103	}
104
105	return state.PeerCertificates[0].Verify(opts)
106}
107
108func (v *basicTLSVerifier) verifyFailure(err error) error {
109	return goerr.New("xmpp: failed to verify TLS certificate: " + err.Error())
110}
111
112func (v *basicTLSVerifier) verifyHostnameFailure(err error) error {
113	return goerr.New("xmpp: failed to match TLS certificate to name: " + err.Error())
114}
115
116func (v *basicTLSVerifier) verifyHostName(leafCert *x509.Certificate, originDomain string) error {
117	return leafCert.VerifyHostname(originDomain)
118}
119
120func (v *basicTLSVerifier) hasPinned(certs []*x509.Certificate) error {
121	savedHash := v.shaSum
122	if len(savedHash) == 0 {
123		return nil
124	}
125
126	if digest := digests.Sha256(certs[0].Raw); !bytes.Equal(digest, savedHash) {
127		return fmt.Errorf("tls: server certificate does not match expected hash (got: %x, want: %x)", digest, savedHash)
128	}
129
130	return nil
131}
132
133func (v *basicTLSVerifier) Verify(state tls.ConnectionState, conf *tls.Config, originDomain string) error {
134	if len(state.PeerCertificates) == 0 {
135		return goerr.New("tls: server has no certificates")
136	}
137
138	if err := v.hasPinned(state.PeerCertificates); err != nil {
139		return err
140	}
141
142	chains, err := v.verifyCert(state, conf)
143	if err != nil {
144		return v.verifyFailure(err)
145	}
146
147	if err = v.verifyHostName(chains[0][0], originDomain); err != nil {
148		return v.verifyHostnameFailure(err)
149	}
150
151	return nil
152}
153
154func (s *ConnectionXMPPSuite) Test_Next_returnsErrorIfOneIsEncountered(c *C) {
155	mockIn := &mockConnIOReaderWriter{read: []byte("<stream:foo xmlns:stream='http://etherx.jabber.org/streams' to='hello'></stream:foo>")}
156	conn := conn{
157		in: xml.NewDecoder(mockIn),
158	}
159
160	_, err := conn.Next()
161	c.Assert(err.Error(), Equals, "unexpected XMPP message http://etherx.jabber.org/streams <foo/>")
162}
163
164func (s *ConnectionXMPPSuite) Test_Next_returnsErrorIfFailingToParseIQID(c *C) {
165	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='abczzzz'></client:iq>")}
166	conn := conn{
167		in: xml.NewDecoder(mockIn),
168	}
169
170	_, err := conn.Next()
171	c.Assert(err.Error(), Equals, "xmpp: failed to parse id from iq: strconv.ParseUint: parsing \"abczzzz\": invalid syntax")
172}
173
174func (s *ConnectionXMPPSuite) Test_Next_returnsNothingIfThereIsNoInflightMatching(c *C) {
175	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000'></client:iq>")}
176	conn := conn{
177		in: xml.NewDecoder(mockIn),
178	}
179
180	_, err := conn.Next()
181	c.Assert(err, Equals, io.EOF)
182}
183
184func (s *ConnectionXMPPSuite) Test_Next_returnsNothingIfTheInflightIsToAnotherReceiver(c *C) {
185	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000' from='bar@somewhere.com'></client:iq>")}
186	conn := conn{
187		in:        xml.NewDecoder(mockIn),
188		inflights: make(map[data.Cookie]inflight),
189	}
190	cookie := data.Cookie(1048576)
191	conn.inflights[cookie] = inflight{to: "foo@somewhere.com"}
192	_, err := conn.Next()
193	c.Assert(err, Equals, io.EOF)
194}
195
196func (s *ConnectionXMPPSuite) Test_Next_removesInflightIfItMatches(c *C) {
197	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000' from='foo@somewhere.com'></client:iq>")}
198	inflights := make(map[data.Cookie]inflight)
199	conn := conn{
200		in:        xml.NewDecoder(mockIn),
201		inflights: inflights,
202	}
203	cookie := data.Cookie(1048576)
204	reply := make(chan data.Stanza, 1)
205	conn.inflights[cookie] =
206		inflight{
207			to:        "foo@somewhere.com",
208			replyChan: reply,
209		}
210
211	go func() {
212		<-reply
213	}()
214
215	_, err := conn.Next()
216	c.Assert(err, Equals, io.EOF)
217	_, ok := conn.inflights[cookie]
218	c.Assert(ok, Equals, false)
219}
220
221func (s *ConnectionXMPPSuite) Test_Next_continuesIfIqFromIsNotSimilarToJid(c *C) {
222	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000' from='foo@somewhere.com'></client:iq>")}
223	inflights := make(map[data.Cookie]inflight)
224	conn := conn{
225		in:        xml.NewDecoder(mockIn),
226		inflights: inflights,
227		jid:       "foo@myjid.com/blah",
228	}
229	cookie := data.Cookie(1048576)
230	conn.inflights[cookie] = inflight{}
231	_, err := conn.Next()
232	c.Assert(err, Equals, io.EOF)
233	_, ok := conn.inflights[cookie]
234	c.Assert(ok, Equals, true)
235}
236
237func (s *ConnectionXMPPSuite) Test_Next_removesIfThereIsNoFrom(c *C) {
238	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000'></client:iq>")}
239	inflights := make(map[data.Cookie]inflight)
240	conn := conn{
241		in:        xml.NewDecoder(mockIn),
242		inflights: inflights,
243	}
244	cookie := data.Cookie(1048576)
245	reply := make(chan data.Stanza, 1)
246	conn.inflights[cookie] =
247		inflight{
248			replyChan: reply,
249		}
250
251	go func() {
252		<-reply
253	}()
254
255	_, err := conn.Next()
256	c.Assert(err, Equals, io.EOF)
257	_, ok := conn.inflights[cookie]
258	c.Assert(ok, Equals, false)
259}
260
261func (s *ConnectionXMPPSuite) Test_Next_removesIfThereIsTheFromIsSameAsJid(c *C) {
262	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000' from='some@one.org/foo'></client:iq>")}
263	inflights := make(map[data.Cookie]inflight)
264	conn := conn{
265		in:        xml.NewDecoder(mockIn),
266		inflights: inflights,
267		jid:       "some@one.org/foo",
268	}
269	cookie := data.Cookie(1048576)
270	reply := make(chan data.Stanza, 1)
271	conn.inflights[cookie] =
272		inflight{
273			replyChan: reply,
274		}
275
276	go func() {
277		<-reply
278	}()
279
280	_, err := conn.Next()
281	c.Assert(err, Equals, io.EOF)
282	_, ok := conn.inflights[cookie]
283	c.Assert(ok, Equals, false)
284}
285
286func (s *ConnectionXMPPSuite) Test_Next_removesIfThereIsTheFromIsSameAsJidWithoutResource(c *C) {
287	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000' from='some@one.org'></client:iq>")}
288	inflights := make(map[data.Cookie]inflight)
289	conn := conn{
290		in:        xml.NewDecoder(mockIn),
291		inflights: inflights,
292		jid:       "some@one.org/foo",
293	}
294	cookie := data.Cookie(1048576)
295	reply := make(chan data.Stanza, 1)
296	conn.inflights[cookie] =
297		inflight{
298			replyChan: reply,
299		}
300
301	go func() {
302		<-reply
303	}()
304
305	_, err := conn.Next()
306	c.Assert(err, Equals, io.EOF)
307	_, ok := conn.inflights[cookie]
308	c.Assert(ok, Equals, false)
309}
310
311func (s *ConnectionXMPPSuite) Test_Next_removesIfThereIsTheFromIsSameAsJidDomain(c *C) {
312	mockIn := &mockConnIOReaderWriter{read: []byte("<client:iq xmlns:client='jabber:client' type='result' id='100000' from='one.org'></client:iq>")}
313	inflights := make(map[data.Cookie]inflight)
314	conn := conn{
315		in:        xml.NewDecoder(mockIn),
316		inflights: inflights,
317		jid:       "some@one.org/foo",
318	}
319	cookie := data.Cookie(1048576)
320	reply := make(chan data.Stanza, 1)
321	conn.inflights[cookie] =
322		inflight{
323			replyChan: reply,
324		}
325
326	go func() {
327		<-reply
328	}()
329
330	_, err := conn.Next()
331	c.Assert(err, Equals, io.EOF)
332	_, ok := conn.inflights[cookie]
333	c.Assert(ok, Equals, false)
334}
335
336func (s *ConnectionXMPPSuite) Test_Next_returnsNonIQMessage(c *C) {
337	mockIn := &mockConnIOReaderWriter{read: []byte("<client:message xmlns:client='jabber:client' to='fo@bar.com' from='bar@foo.com' type='chat'><client:body>something</client:body></client:message>")}
338	conn := conn{
339		in:  xml.NewDecoder(mockIn),
340		jid: "some@one.org/foo",
341	}
342	v, err := conn.Next()
343	c.Assert(err, IsNil)
344	c.Assert(v.Value.(*data.ClientMessage).From, Equals, "bar@foo.com")
345	c.Assert(v.Value.(*data.ClientMessage).To, Equals, "fo@bar.com")
346	c.Assert(v.Value.(*data.ClientMessage).Type, Equals, "chat")
347	c.Assert(v.Value.(*data.ClientMessage).Body, Equals, "something")
348}
349
350func (s *ConnectionXMPPSuite) Test_makeInOut_returnsANewDecoderAndOriginalWriterWhenNoConfigIsGiven(c *C) {
351	mockBoth := &mockConnIOReaderWriter{}
352	_, rout := makeInOut(mockBoth, data.Config{})
353	c.Assert(rout, Equals, mockBoth)
354}
355
356func (s *ConnectionXMPPSuite) Test_makeInOut_returnsANewDecoderAndWrappedWriterWhenConfigIsGiven(c *C) {
357	mockBoth := &mockConnIOReaderWriter{}
358	mockInLog := &mockConnIOReaderWriter{}
359	config := data.Config{InLog: mockInLog, OutLog: mockInLog}
360	_, rout := makeInOut(mockBoth, config)
361	c.Assert(rout, Not(Equals), mockBoth)
362}
363
364func (s *ConnectionXMPPSuite) Test_Dial_returnsErrorFromGetFeatures(c *C) {
365	rw := &mockConnIOReaderWriter{}
366	conn := &fullMockedConn{rw: rw}
367
368	d := &dialer{
369		JID:      "user@domain",
370		password: "pass",
371	}
372	_, err := d.setupStream(conn)
373
374	c.Assert(err, Equals, io.EOF)
375}
376
377func (s *ConnectionXMPPSuite) Test_Dial_returnsErrorFromAuthenticateIfSkipTLS(c *C) {
378	rw := &mockConnIOReaderWriter{read: []byte("<?xml version='1.0'?><str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'><str:features></str:features>")}
379	conn := &fullMockedConn{rw: rw}
380
381	d := &dialer{
382		JID:      "user@domain",
383		password: "pass",
384		config:   data.Config{SkipTLS: true},
385	}
386	_, err := d.setupStream(conn)
387
388	c.Assert(err, Equals, errors.ErrAuthenticationFailed)
389}
390
391func (s *ConnectionXMPPSuite) Test_Dial_returnsErrorFromSecondFeatureCheck(c *C) {
392	rw := &mockConnIOReaderWriter{read: []byte(
393		"<?xml version='1.0'?>" +
394			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
395			"<str:features>" +
396			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
397			"<mechanism>PLAIN</mechanism>" +
398			"</mechanisms>" +
399			"</str:features>" +
400			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>")}
401	conn := &fullMockedConn{rw: rw}
402
403	d := &dialer{
404		JID:      "user@domain",
405		password: "pass",
406		config:   data.Config{SkipTLS: true},
407	}
408	_, err := d.setupStream(conn)
409
410	c.Assert(err.Error(), Matches, "(XML syntax error on line 1: unexpected )?EOF")
411
412	c.Assert(string(rw.write), Equals, ""+
413		"<?xml version='1.0'?>"+
414		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
415		"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHVzZXIAcGFzcw==</auth>\n"+
416		"<?xml version='1.0'?>"+
417		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n")
418}
419
420func (s *ConnectionXMPPSuite) Test_Dial_returnsErrorFromIQReturn(c *C) {
421	rw := &mockConnIOReaderWriter{read: []byte(
422		"<?xml version='1.0'?>" +
423			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
424			"<str:features>" +
425			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
426			"<mechanism>PLAIN</mechanism>" +
427			"</mechanisms>" +
428			"</str:features>" +
429			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>" +
430			"<?xml version='1.0'?>" +
431			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
432			"<str:features>" +
433			"</str:features>",
434	)}
435	conn := &fullMockedConn{rw: rw}
436
437	d := &dialer{
438		JID:      "user@domain",
439		password: "pass",
440		config:   data.Config{SkipTLS: true},
441	}
442	_, err := d.setupStream(conn)
443
444	c.Assert(err.Error(), Matches, "unmarshal <iq>:( XML syntax error on line 1: unexpected)? EOF")
445	c.Assert(string(rw.write), Equals, ""+
446		"<?xml version='1.0'?>"+
447		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
448		"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHVzZXIAcGFzcw==</auth>\n"+
449		"<?xml version='1.0'?>"+
450		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
451		"<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>",
452	)
453}
454
455func (s *ConnectionXMPPSuite) Test_Dial_returnsWorkingConnIfEverythingPasses(c *C) {
456	rw := &mockConnIOReaderWriter{read: []byte(
457		"<?xml version='1.0'?>" +
458			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
459			"<str:features>" +
460			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
461			"<mechanism>PLAIN</mechanism>" +
462			"</mechanisms>" +
463			"</str:features>" +
464			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>" +
465			"<?xml version='1.0'?>" +
466			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
467			"<str:features>" +
468			"</str:features>" +
469			"<client:iq xmlns:client='jabber:client'></client:iq>",
470	)}
471	conn := &fullMockedConn{rw: rw}
472
473	d := &dialer{
474		JID:      "user@domain",
475		password: "pass",
476		config:   data.Config{SkipTLS: true},
477	}
478	_, err := d.setupStream(conn)
479
480	c.Assert(err, IsNil)
481	c.Assert(string(rw.write), Equals, ""+
482		"<?xml version='1.0'?>"+
483		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
484		"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHVzZXIAcGFzcw==</auth>\n"+
485		"<?xml version='1.0'?>"+
486		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
487		"<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>",
488	)
489}
490
491func (s *ConnectionXMPPSuite) Test_Dial_failsIfTheServerDoesntSupportTLS(c *C) {
492	rw := &mockConnIOReaderWriter{read: []byte(
493		"<?xml version='1.0'?>" +
494			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
495			"<str:features>" +
496			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
497			"<mechanism>PLAIN</mechanism>" +
498			"</mechanisms>" +
499			"</str:features>" +
500			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>",
501	)}
502	conn := &fullMockedConn{rw: rw}
503
504	d := &dialer{
505		JID:      "user@domain",
506		password: "pass",
507	}
508	_, err := d.setupStream(conn)
509
510	c.Assert(err.Error(), Equals, "xmpp: server doesn't support TLS")
511}
512
513func (s *ConnectionXMPPSuite) Test_Dial_failsIfReceivingEOFAfterStartingTLS(c *C) {
514	rw := &mockConnIOReaderWriter{read: []byte(
515		"<?xml version='1.0'?>" +
516			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
517			"<str:features>" +
518			"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>" +
519			"</starttls>" +
520			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
521			"<mechanism>PLAIN</mechanism>" +
522			"</mechanisms>" +
523			"</str:features>",
524	)}
525	conn := &fullMockedConn{rw: rw}
526
527	d := &dialer{
528		JID:      "user@domain",
529		password: "pass",
530	}
531	_, err := d.setupStream(conn)
532
533	c.Assert(err.Error(), Matches, "(XML syntax error on line 1: unexpected )?EOF")
534}
535
536func (s *ConnectionXMPPSuite) Test_Dial_failsIfReceivingTheWrongNamespaceAfterStarttls(c *C) {
537	rw := &mockConnIOReaderWriter{read: []byte(
538		"<?xml version='1.0'?>" +
539			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
540			"<str:features>" +
541			"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>" +
542			"</starttls>" +
543			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
544			"<mechanism>PLAIN</mechanism>" +
545			"</mechanisms>" +
546			"</str:features>" +
547			"<str:proceed>",
548	)}
549	conn := &fullMockedConn{rw: rw}
550
551	d := &dialer{
552		JID:      "user@domain",
553		password: "pass",
554	}
555	_, err := d.setupStream(conn)
556
557	c.Assert(err.Error(), Equals, "xmpp: expected <proceed> after <starttls> but got <proceed> in http://etherx.jabber.org/streams")
558}
559
560func (s *ConnectionXMPPSuite) Test_Dial_failsIfReceivingTheWrongTagName(c *C) {
561	rw := &mockConnIOReaderWriter{read: []byte(
562		"<?xml version='1.0'?>" +
563			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
564			"<str:features>" +
565			"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>" +
566			"</starttls>" +
567			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
568			"<mechanism>PLAIN</mechanism>" +
569			"</mechanisms>" +
570			"</str:features>" +
571			"<things xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>",
572	)}
573	conn := &fullMockedConn{rw: rw}
574
575	d := &dialer{
576		JID:      "user@domain",
577		password: "pass",
578	}
579	_, err := d.setupStream(conn)
580
581	c.Assert(err.Error(), Equals, "xmpp: expected <proceed> after <starttls> but got <things> in urn:ietf:params:xml:ns:xmpp-tls")
582}
583
584func (s *ConnectionXMPPSuite) Test_Dial_failsIfDecodingFallbackFails(c *C) {
585	rw := &mockConnIOReaderWriter{read: []byte(
586		"<?xml version='1.0'?>" +
587			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
588			"<str:features>" +
589			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
590			"<mechanism>PLAIN</mechanism>" +
591			"</mechanisms>" +
592			"<register xmlns='http://jabber.org/features/iq-register'/>" +
593			"</str:features>",
594	)}
595	conn := &fullMockedConn{rw: rw}
596
597	d := &dialer{
598		JID:      "user@domain",
599		password: "pass",
600		config: data.Config{
601			SkipTLS: true,
602			CreateCallback: func(title, instructions string, fields []interface{}) error {
603				return nil
604			},
605		},
606	}
607	_, err := d.setupStream(conn)
608
609	c.Assert(err.Error(), Matches, "unmarshal <iq>:( XML syntax error on line 1: unexpected)? EOF")
610	c.Assert(string(rw.write), Equals, ""+
611		"<?xml version='1.0'?>"+
612		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
613		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>",
614	)
615}
616
617func (s *ConnectionXMPPSuite) Test_Dial_failsIfAccountCreationFails(c *C) {
618	rw := &mockConnIOReaderWriter{read: []byte(
619		"<?xml version='1.0'?>" +
620			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
621			"<str:features>" +
622			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
623			"<mechanism>PLAIN</mechanism>" +
624			"</mechanisms>" +
625			"<register xmlns='http://jabber.org/features/iq-register'/>" +
626			"</str:features>" +
627			"<iq xmlns='jabber:client' type='something'></iq>",
628	)}
629	conn := &fullMockedConn{rw: rw}
630
631	d := &dialer{
632		JID:      "user@domain",
633		password: "pass",
634		config: data.Config{
635			SkipTLS: true,
636			CreateCallback: func(title, instructions string, fields []interface{}) error {
637				return nil
638			},
639		},
640	}
641	_, err := d.setupStream(conn)
642
643	c.Assert(err.Error(), Equals, "xmpp: account creation failed")
644	c.Assert(string(rw.write), Equals, ""+
645		"<?xml version='1.0'?>"+
646		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
647		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>",
648	)
649}
650
651func (s *ConnectionXMPPSuite) Test_Dial_failsIfTheIQQueryHasNoContent(c *C) {
652	rw := &mockConnIOReaderWriter{read: []byte(
653		"<?xml version='1.0'?>" +
654			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
655			"<str:features>" +
656			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
657			"<mechanism>PLAIN</mechanism>" +
658			"</mechanisms>" +
659			"<register xmlns='http://jabber.org/features/iq-register'/>" +
660			"</str:features>" +
661			"<iq xmlns='jabber:client' type='result'></iq>",
662	)}
663	conn := &fullMockedConn{rw: rw}
664
665	d := &dialer{
666		JID:      "user@domain",
667		password: "pass",
668		config: data.Config{
669			SkipTLS: true,
670			CreateCallback: func(title, instructions string, fields []interface{}) error {
671				return nil
672			},
673		},
674	}
675	_, err := d.setupStream(conn)
676
677	c.Assert(err, Equals, io.EOF)
678	c.Assert(string(rw.write), Equals, ""+
679		"<?xml version='1.0'?>"+
680		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
681		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>",
682	)
683}
684
685func (s *ConnectionXMPPSuite) Test_Dial_ifRegisterQueryDoesntContainDataFailsAtNextIQ(c *C) {
686	rw := &mockConnIOReaderWriter{read: []byte(
687		"<?xml version='1.0'?>" +
688			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
689			"<str:features>" +
690			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
691			"<mechanism>PLAIN</mechanism>" +
692			"</mechanisms>" +
693			"<register xmlns='http://jabber.org/features/iq-register'/>" +
694			"</str:features>" +
695			"<iq xmlns='jabber:client' type='result'>" +
696			"<query xmlns='jabber:iq:register'></query>" +
697			"</iq>",
698	)}
699	conn := &fullMockedConn{rw: rw}
700
701	d := &dialer{
702		JID:      "user@domain",
703		password: "pass",
704		config: data.Config{
705			SkipTLS: true,
706			CreateCallback: func(title, instructions string, fields []interface{}) error {
707				return nil
708			},
709		},
710	}
711	_, err := d.setupStream(conn)
712
713	c.Assert(err.Error(), Matches, "unmarshal <iq>:( XML syntax error on line 1: unexpected)? EOF")
714	c.Assert(string(rw.write), Equals, ""+
715		"<?xml version='1.0'?>"+
716		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
717		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>",
718	)
719}
720
721func (s *ConnectionXMPPSuite) Test_Dial_afterRegisterFailsIfReceivesAnErrorElement(c *C) {
722	rw := &mockConnIOReaderWriter{read: []byte(
723		"<?xml version='1.0'?>" +
724			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
725			"<str:features>" +
726			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
727			"<mechanism>PLAIN</mechanism>" +
728			"</mechanisms>" +
729			"<register xmlns='http://jabber.org/features/iq-register'/>" +
730			"</str:features>" +
731			"<iq xmlns='jabber:client' type='result'>" +
732			"<query xmlns='jabber:iq:register'></query>" +
733			"</iq>" +
734			"<iq xmlns='jabber:client' type='error'></iq>",
735	)}
736	conn := &fullMockedConn{rw: rw}
737
738	d := &dialer{
739		JID:      "user@domain",
740		password: "pass",
741		config: data.Config{
742			SkipTLS: true,
743			CreateCallback: func(title, instructions string, fields []interface{}) error {
744				return nil
745			},
746		},
747	}
748	_, err := d.setupStream(conn)
749
750	c.Assert(err.Error(), Equals, "xmpp: account creation failed")
751	c.Assert(string(rw.write), Equals, ""+
752		"<?xml version='1.0'?>"+
753		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
754		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>",
755	)
756}
757
758func (s *ConnectionXMPPSuite) Test_Dial_sendsBackUsernameAndPassword(c *C) {
759	rw := &mockConnIOReaderWriter{read: []byte(
760		"<?xml version='1.0'?>" +
761			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
762			"<str:features>" +
763			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
764			"<mechanism>PLAIN</mechanism>" +
765			"</mechanisms>" +
766			"<register xmlns='http://jabber.org/features/iq-register'/>" +
767			"</str:features>" +
768			"<iq xmlns='jabber:client' type='result'>" +
769			"<query xmlns='jabber:iq:register'><username/><password/></query>" +
770			"</iq>" +
771			"<iq xmlns='jabber:client' type='result'></iq>",
772	)}
773	conn := &fullMockedConn{rw: rw}
774
775	d := &dialer{
776		JID:      "user@domain",
777		password: "pass",
778		config: data.Config{
779			SkipTLS: true,
780			CreateCallback: func(title, instructions string, fields []interface{}) error {
781				return nil
782			},
783		},
784	}
785	_, err := d.setupStream(conn)
786
787	c.Assert(err, IsNil)
788	c.Assert(string(rw.write), Equals, ""+
789		"<?xml version='1.0'?>"+
790		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
791		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>"+
792		"<iq type='set' id='create_2'><query xmlns='jabber:iq:register'><username>user</username><password>pass</password></query></iq>"+
793		"</stream:stream>",
794	)
795}
796
797func (s *ConnectionXMPPSuite) Test_Dial_runsForm(c *C) {
798	rw := &mockConnIOReaderWriter{read: []byte(
799		"<?xml version='1.0'?>" +
800			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
801			"<str:features>" +
802			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
803			"<mechanism>PLAIN</mechanism>" +
804			"</mechanisms>" +
805			"<register xmlns='http://jabber.org/features/iq-register'/>" +
806			"</str:features>" +
807			"<iq xmlns='jabber:client' type='result'>" +
808			"<query xmlns='jabber:iq:register'>" +
809			"<x xmlns='jabber:x:data' type='form'>" +
810			"<title>Contest Registration</title>" +
811			"<field type='hidden' var='FORM_TYPE'>" +
812			"<value>jabber:iq:register</value>" +
813			"</field>" +
814			"<field type='text-single' label='Given Name' var='first'>" +
815			"<required/>" +
816			"</field>" +
817			"</x>" +
818			"</query>" +
819			"</iq>" +
820			"<iq xmlns='jabber:client' type='result'></iq>",
821	)}
822	conn := &fullMockedConn{rw: rw}
823
824	d := &dialer{
825		JID:      "user@domain",
826		password: "pass",
827		config: data.Config{
828			SkipTLS: true,
829			CreateCallback: func(title, instructions string, fields []interface{}) error {
830				return nil
831			},
832		},
833	}
834
835	_, err := d.setupStream(conn)
836
837	c.Assert(err, IsNil)
838	c.Assert(string(rw.write), Equals, ""+
839		"<?xml version='1.0'?>"+
840		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
841		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>"+
842		"<iq type='set' id='create_2'><query xmlns='jabber:iq:register'><x xmlns=\"jabber:x:data\" type=\"submit\"><field var=\"FORM_TYPE\"><value>jabber:iq:register</value></field><field var=\"first\"><value></value></field></x></query></iq>"+
843		"</stream:stream>",
844	)
845}
846
847func (s *ConnectionXMPPSuite) Test_Dial_setsLog(c *C) {
848	l := &mockConnIOReaderWriter{}
849	rw := &mockConnIOReaderWriter{read: []byte(
850		"<?xml version='1.0'?>" +
851			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
852			"<str:features>" +
853			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
854			"<mechanism>PLAIN</mechanism>" +
855			"</mechanisms>" +
856			"<register xmlns='http://jabber.org/features/iq-register'/>" +
857			"</str:features>",
858	)}
859	conn := &fullMockedConn{rw: rw}
860
861	d := &dialer{
862		JID:      "user@domain",
863		password: "pass",
864		config: data.Config{
865			SkipTLS: true,
866			Log:     l,
867			CreateCallback: func(title, instructions string, fields []interface{}) error {
868				return nil
869			},
870		},
871	}
872	_, err := d.setupStream(conn)
873
874	c.Assert(err.Error(), Matches, "unmarshal <iq>:( XML syntax error on line 1: unexpected)? EOF")
875	c.Assert(string(l.write), Equals, "Attempting to create account\n")
876	c.Assert(string(rw.write), Equals, ""+
877		"<?xml version='1.0'?>"+
878		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
879		"<iq type='get' id='create_1'><query xmlns='jabber:iq:register'/></iq>",
880	)
881}
882
883func (s *ConnectionXMPPSuite) Test_Dial_failsWhenTryingToEstablishSession(c *C) {
884	rw := &mockConnIOReaderWriter{read: []byte(
885		"<?xml version='1.0'?>" +
886			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
887			"<str:features>" +
888			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
889			"<mechanism>PLAIN</mechanism>" +
890			"</mechanisms>" +
891			"</str:features>" +
892			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>" +
893			"<?xml version='1.0'?>" +
894			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
895			"<str:features>" +
896			"<str:session>foobar</str:session>" +
897			"</str:features>" +
898			"<client:iq xmlns:client='jabber:client'></client:iq>",
899	)}
900	conn := &fullMockedConn{rw: rw}
901
902	d := &dialer{
903		JID:      "user@domain",
904		password: "pass",
905		config: data.Config{
906			SkipTLS: true,
907		},
908	}
909	_, err := d.setupStream(conn)
910
911	c.Assert(err.Error(), Matches, "xmpp: unmarshal <iq>:( XML syntax error on line 1: unexpected)? EOF")
912
913	c.Assert(string(rw.write), Equals, ""+
914		"<?xml version='1.0'?>"+
915		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
916		"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHVzZXIAcGFzcw==</auth>\n"+
917		"<?xml version='1.0'?>"+
918		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
919		"<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>"+
920		"<iq to='domain' type='set' id='sess_1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>",
921	)
922}
923
924func (s *ConnectionXMPPSuite) Test_Dial_failsWhenTryingToEstablishSessionAndGetsTheWrongIQBack(c *C) {
925	rw := &mockConnIOReaderWriter{read: []byte(
926		"<?xml version='1.0'?>" +
927			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
928			"<str:features>" +
929			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
930			"<mechanism>PLAIN</mechanism>" +
931			"</mechanisms>" +
932			"</str:features>" +
933			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>" +
934			"<?xml version='1.0'?>" +
935			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
936			"<str:features>" +
937			"<str:session>foobar</str:session>" +
938			"</str:features>" +
939			"<client:iq xmlns:client='jabber:client'></client:iq>" +
940			"<client:iq xmlns:client='jabber:client' type='foo'></client:iq>",
941	)}
942	conn := &fullMockedConn{rw: rw}
943
944	d := &dialer{
945		JID:      "user@domain",
946		password: "pass",
947		config: data.Config{
948			SkipTLS: true,
949		},
950	}
951	_, err := d.setupStream(conn)
952
953	c.Assert(err.Error(), Equals, "xmpp: session establishment failed")
954	c.Assert(string(rw.write), Equals, ""+
955		"<?xml version='1.0'?>"+
956		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
957		"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHVzZXIAcGFzcw==</auth>\n"+
958		"<?xml version='1.0'?>"+
959		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
960		"<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>"+
961		"<iq to='domain' type='set' id='sess_1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>",
962	)
963}
964
965func (s *ConnectionXMPPSuite) Test_Dial_succeedsEstablishingASession(c *C) {
966	rw := &mockConnIOReaderWriter{read: []byte(
967		"<?xml version='1.0'?>" +
968			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
969			"<str:features>" +
970			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
971			"<mechanism>PLAIN</mechanism>" +
972			"</mechanisms>" +
973			"</str:features>" +
974			"<sasl:success xmlns:sasl='urn:ietf:params:xml:ns:xmpp-sasl'></sasl:success>" +
975			"<?xml version='1.0'?>" +
976			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
977			"<str:features>" +
978			"<str:session>foobar</str:session>" +
979			"</str:features>" +
980			"<client:iq xmlns:client='jabber:client'></client:iq>" +
981			"<client:iq xmlns:client='jabber:client' type='result'></client:iq>",
982	)}
983	conn := &fullMockedConn{rw: rw}
984
985	d := &dialer{
986		JID:      "user@domain",
987		password: "pass",
988		config: data.Config{
989			SkipTLS: true,
990		},
991	}
992	_, err := d.setupStream(conn)
993
994	c.Assert(err, IsNil)
995	c.Assert(string(rw.write), Equals, ""+
996		"<?xml version='1.0'?>"+
997		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
998		"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHVzZXIAcGFzcw==</auth>\n"+
999		"<?xml version='1.0'?>"+
1000		"<stream:stream to='domain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"+
1001		"<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>"+
1002		"<iq to='domain' type='set' id='sess_1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>",
1003	)
1004}
1005
1006func (s *ConnectionXMPPSuite) Test_readMessages_passesStanzaToChannel(c *C) {
1007	mockIn := &mockConnIOReaderWriter{read: []byte("<client:message xmlns:client='jabber:client' to='fo@bar.com' from='bar@foo.com' type='chat'><client:body>something</client:body></client:message>")}
1008
1009	conn := &conn{
1010		in:     xml.NewDecoder(mockIn),
1011		closed: true, //This avoids trying to close the connection after the EOF
1012	}
1013	stanzaChan := make(chan data.Stanza)
1014	go conn.ReadStanzas(stanzaChan)
1015
1016	select {
1017	case rawStanza, ok := <-stanzaChan:
1018		c.Assert(ok, Equals, true)
1019		c.Assert(rawStanza.Name.Local, Equals, "message")
1020		c.Assert(rawStanza.Value.(*data.ClientMessage).Body, Equals, "something")
1021	}
1022}
1023
1024func (s *ConnectionXMPPSuite) Test_readMessages_alertsOnError(c *C) {
1025	mockIn := &mockConnIOReaderWriter{read: []byte("<clientx:message xmlns:client='jabber:client' to='fo@bar.com' from='bar@foo.com' type='chat'><client:body>something</client:body></client:message>")}
1026
1027	conn := &conn{
1028		in:     xml.NewDecoder(mockIn),
1029		closed: true, //This avoids trying to close the connection after the EOF
1030	}
1031
1032	stanzaChan := make(chan data.Stanza, 1)
1033	err := conn.ReadStanzas(stanzaChan)
1034
1035	select {
1036	case _, ok := <-stanzaChan:
1037		c.Assert(ok, Equals, false)
1038	}
1039
1040	c.Assert(err.Error(), Equals, "unexpected XMPP message clientx <message/>")
1041}
1042
1043func (s *ConnectionXMPPSuite) Test_Dial_failsWhenStartingAHandshake(c *C) {
1044	tlsC, v := tlsConfigForRecordedHandshake()
1045
1046	rw := &mockConnIOReaderWriter{read: []byte(
1047		"<?xml version='1.0'?>" +
1048			"<str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'>" +
1049			"<str:features>" +
1050			"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>" +
1051			"</starttls>" +
1052			"<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
1053			"<mechanism>PLAIN</mechanism>" +
1054			"</mechanisms>" +
1055			"</str:features>" +
1056			"<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>",
1057	)}
1058	t := &tlsMock1{returnFromHandshake: io.EOF}
1059	d := &dialer{
1060		JID:      "user@domain",
1061		password: "pass",
1062
1063		verifier: v,
1064		config: data.Config{
1065			TLSConfig: tlsC,
1066		},
1067		tlsConnFactory: fixedTlsFactory(t),
1068	}
1069
1070	conn := &fullMockedConn{rw: rw}
1071	_, err := d.setupStream(conn)
1072
1073	c.Assert(err, Equals, io.EOF)
1074}
1075
1076func (s *ConnectionXMPPSuite) Test_Dial_worksIfTheHandshakeSucceeds(c *C) {
1077	tlsC, _ := tlsConfigForRecordedHandshake()
1078
1079	rw := &mockMultiConnIOReaderWriter{read: validTLSExchange}
1080	conn := &fullMockedConn{rw: rw}
1081	connState := tls.ConnectionState{
1082		Version:           tls.VersionTLS12,
1083		HandshakeComplete: true,
1084		CipherSuite:       tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
1085	}
1086	t := &tlsMock1{
1087		returnFromHandshake: nil,
1088		returnFromConnState: connState,
1089		returnFromRead1:     0,
1090		returnFromRead2:     io.EOF,
1091	}
1092	v := &mockTLSVerifier{
1093		toReturn: nil,
1094	}
1095	d := &dialer{
1096		JID:           "user@www.olabini.se",
1097		password:      "pass",
1098		serverAddress: "www.olabini.se:443",
1099		verifier:      v,
1100		config: data.Config{
1101			TLSConfig: tlsC,
1102		},
1103		tlsConnFactory: fixedTlsFactory(t),
1104	}
1105	_, err := d.setupStream(conn)
1106
1107	c.Assert(err, Equals, io.EOF)
1108	c.Assert(v.verifyCalled, Equals, 1)
1109	c.Assert(v.originDomain, Equals, "www.olabini.se")
1110}
1111
1112func (s *ConnectionXMPPSuite) Test_Dial_worksIfTheHandshakeSucceedsButFailsOnInvalidCertHash(c *C) {
1113	tlsC, _ := tlsConfigForRecordedHandshake()
1114
1115	rw := &mockMultiConnIOReaderWriter{read: validTLSExchange}
1116	conn := &fullMockedConn{rw: rw}
1117	t := &tlsMock1{
1118		returnFromHandshake: nil,
1119		returnFromConnState: tls.ConnectionState{},
1120	}
1121	v := &mockTLSVerifier{
1122		toReturn: goerr.New("tls: server certificate does not match expected hash (got: 82454418cb04854aa721bb0596528ff802b1e18a4e3a7767412ac9f108c9d3a7, want: 6161616161)"),
1123	}
1124	d := &dialer{
1125		JID:           "user@www.olabini.se",
1126		password:      "pass",
1127		serverAddress: "www.olabini.se:443",
1128		verifier:      v,
1129
1130		config: data.Config{
1131			TLSConfig: tlsC,
1132		},
1133		tlsConnFactory: fixedTlsFactory(t),
1134	}
1135	_, err := d.setupStream(conn)
1136
1137	c.Assert(err.Error(), Equals, "tls: server certificate does not match expected hash (got: 82454418cb04854aa721bb0596528ff802b1e18a4e3a7767412ac9f108c9d3a7, want: 6161616161)")
1138}
1139
1140func (s *ConnectionXMPPSuite) Test_Dial_worksIfTheHandshakeSucceedsButSucceedsOnValidCertHash(c *C) {
1141	tlsC, _ := tlsConfigForRecordedHandshake()
1142	tlsC.Rand = fixedRand([]string{
1143		"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
1144		"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
1145		"000102030405060708090A0B0C0D0E0F",
1146		"000102030405060708090A0B0C0D0E0F",
1147	})
1148
1149	rw := &mockMultiConnIOReaderWriter{read: validTLSExchange}
1150	conn := &fullMockedConn{rw: rw}
1151	connState := tls.ConnectionState{
1152		Version:           tls.VersionTLS12,
1153		HandshakeComplete: true,
1154		CipherSuite:       tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
1155	}
1156	t := &tlsMock1{
1157		returnFromHandshake: nil,
1158		returnFromConnState: connState,
1159		returnFromRead1:     0,
1160		returnFromRead2:     io.EOF,
1161	}
1162	v := &mockTLSVerifier{
1163		toReturn: nil,
1164	}
1165	d := &dialer{
1166		JID:           "user@www.olabini.se",
1167		password:      "pass",
1168		serverAddress: "www.olabini.se:443",
1169		verifier:      v,
1170
1171		config: data.Config{
1172			TLSConfig: tlsC,
1173		},
1174		tlsConnFactory: fixedTlsFactory(t),
1175	}
1176	_, err := d.setupStream(conn)
1177
1178	c.Assert(err, Equals, io.EOF)
1179	c.Assert(v.verifyCalled, Equals, 1)
1180	c.Assert(v.originDomain, Equals, "www.olabini.se")
1181}
1182