go-xmpp/xmpp_transport.go

161 lines
3.9 KiB
Go
Raw Permalink Normal View History

2019-10-10 21:32:26 -07:00
package xmpp
import (
"bufio"
2019-10-10 21:32:26 -07:00
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"io"
2019-10-10 21:32:26 -07:00
"net"
"time"
"gosrc.io/xmpp/stanza"
2019-10-10 21:32:26 -07:00
)
// XMPPTransport implements the XMPP native TCP transport
// The decoder is expected to be initialized after connecting to a server.
2019-10-10 21:32:26 -07:00
type XMPPTransport struct {
2019-10-25 06:22:01 -07:00
openStatement string
Config TransportConfiguration
TLSConfig *tls.Config
decoder *xml.Decoder
conn net.Conn
readWriter io.ReadWriter
logFile io.Writer
isSecure bool
2020-03-06 07:44:01 -08:00
// Used to close TCP connection when a stream close message is received from the server
closeChan chan stanza.StreamClosePacket
2019-10-10 21:32:26 -07:00
}
2019-10-25 06:22:01 -07:00
var componentStreamOpen = fmt.Sprintf("<?xml version='1.0'?><stream:stream to='%%s' xmlns='%s' xmlns:stream='%s'>", stanza.NSComponent, stanza.NSStream)
var clientStreamOpen = fmt.Sprintf("<?xml version='1.0'?><stream:stream to='%%s' xmlns='%s' xmlns:stream='%s' version='1.0'>", stanza.NSClient, stanza.NSStream)
func (t *XMPPTransport) Connect() (string, error) {
2019-10-10 21:32:26 -07:00
var err error
t.conn, err = net.DialTimeout("tcp", t.Config.Address, time.Duration(t.Config.ConnectTimeout)*time.Second)
if err != nil {
return "", NewConnError(err, true)
}
t.closeChan = make(chan stanza.StreamClosePacket)
t.readWriter = newStreamLogger(t.conn, t.logFile)
t.decoder = xml.NewDecoder(bufio.NewReaderSize(t.readWriter, maxPacketSize))
t.decoder.CharsetReader = t.Config.CharsetReader
return t.StartStream()
2019-10-25 06:22:01 -07:00
}
func (t *XMPPTransport) StartStream() (string, error) {
if _, err := fmt.Fprintf(t, t.openStatement, t.Config.Domain); err != nil {
t.Close()
return "", NewConnError(err, true)
}
sessionID, err := stanza.InitStream(t.GetDecoder())
if err != nil {
t.Close()
return "", NewConnError(err, false)
}
return sessionID, nil
2019-10-10 21:32:26 -07:00
}
func (t *XMPPTransport) DoesStartTLS() bool {
2019-10-10 21:32:26 -07:00
return true
}
func (t *XMPPTransport) GetDomain() string {
return t.Config.Domain
}
func (t *XMPPTransport) GetDecoder() *xml.Decoder {
return t.decoder
}
func (t *XMPPTransport) IsSecure() bool {
2019-10-10 22:15:47 -07:00
return t.isSecure
}
func (t *XMPPTransport) StartTLS() error {
2019-10-10 21:32:26 -07:00
if t.Config.TLSConfig == nil {
2019-10-12 11:47:16 -07:00
t.TLSConfig = &tls.Config{}
} else {
t.TLSConfig = t.Config.TLSConfig.Clone()
2019-10-10 21:32:26 -07:00
}
2019-10-12 11:47:16 -07:00
if t.TLSConfig.ServerName == "" {
t.TLSConfig.ServerName = t.Config.Domain
2019-10-10 21:32:26 -07:00
}
tlsConn := tls.Client(t.conn, t.TLSConfig)
// We convert existing connection to TLS
if err := tlsConn.Handshake(); err != nil {
return err
2019-10-10 21:32:26 -07:00
}
2020-03-10 09:14:26 -07:00
t.isSecure = false
t.conn = tlsConn
t.readWriter = newStreamLogger(tlsConn, t.logFile)
t.decoder = xml.NewDecoder(bufio.NewReaderSize(t.readWriter, maxPacketSize))
t.decoder.CharsetReader = t.Config.CharsetReader
2019-10-10 21:32:26 -07:00
if !t.TLSConfig.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(t.Config.Domain); err != nil {
return err
2019-10-10 21:32:26 -07:00
}
}
2019-10-10 22:15:47 -07:00
t.isSecure = true
return nil
2019-10-10 21:32:26 -07:00
}
func (t *XMPPTransport) Ping() error {
n, err := t.conn.Write([]byte("\n"))
if err != nil {
return err
}
if n != 1 {
return errors.New("could not write ping")
}
return nil
}
func (t *XMPPTransport) Read(p []byte) (n int, err error) {
if t.readWriter == nil {
return 0, errors.New("cannot read: not connected, no readwriter")
}
return t.readWriter.Read(p)
2019-10-10 21:32:26 -07:00
}
func (t *XMPPTransport) Write(p []byte) (n int, err error) {
if t.readWriter == nil {
return 0, errors.New("cannot write: not connected, no readwriter")
}
return t.readWriter.Write(p)
2019-10-10 21:32:26 -07:00
}
func (t *XMPPTransport) Close() error {
if t.readWriter != nil {
_, _ = t.readWriter.Write([]byte(stanza.StreamClose))
}
// Try to wait for the stream close tag from the server. After a timeout, disconnect anyway.
select {
case <-t.closeChan:
case <-time.After(time.Duration(t.Config.ConnectTimeout) * time.Second):
}
if t.conn != nil {
return t.conn.Close()
}
return nil
2019-10-10 21:32:26 -07:00
}
func (t *XMPPTransport) LogTraffic(logFile io.Writer) {
t.logFile = logFile
}
func (t *XMPPTransport) ReceivedStreamClose() {
t.closeChan <- stanza.StreamClosePacket{}
}