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