go-xmpp/component.go

146 lines
4 KiB
Go
Raw Normal View History

2018-01-11 21:15:54 +00:00
package xmpp
import (
"crypto/sha1"
"encoding/hex"
2018-01-11 22:00:59 +00:00
"encoding/xml"
"errors"
2018-01-11 21:15:54 +00:00
"fmt"
2018-01-11 22:00:59 +00:00
"io"
2018-01-11 21:15:54 +00:00
"net"
"time"
)
const componentStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s'>"
// Component implements an XMPP extension allowing to extend XMPP server
// using external components. Component specifications are defined
// in XEP-0114, XEP-0355 and XEP-0356.
type Component struct {
Host string
Secret string
2018-01-11 21:15:54 +00:00
// TCP level connection
conn net.Conn
2018-01-11 22:00:59 +00:00
// read / write
socketProxy io.ReadWriter // TODO
2018-01-11 22:00:59 +00:00
decoder *xml.Decoder
2018-01-11 21:15:54 +00:00
}
2018-01-25 22:16:55 +00:00
// Connect triggers component connection to XMPP server component port.
2018-01-11 21:15:54 +00:00
// TODO Helper to prepare connection string
2018-01-12 17:14:41 +00:00
func (c *Component) Connect(connStr string) error {
2018-01-11 21:15:54 +00:00
var conn net.Conn
var err error
2018-01-12 17:14:41 +00:00
if conn, err = net.DialTimeout("tcp", connStr, time.Duration(5)*time.Second); err != nil {
2018-01-11 21:15:54 +00:00
return err
}
2018-01-11 22:00:59 +00:00
c.conn = conn
2018-01-11 21:15:54 +00:00
2018-01-12 17:14:41 +00:00
// 1. Send stream open tag
if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Host, NSComponent, NSStream); err != nil {
return errors.New("cannot send stream open " + err.Error())
2018-01-11 22:00:59 +00:00
}
c.decoder = xml.NewDecoder(conn)
2018-01-12 17:14:41 +00:00
// 2. Initialize xml decoder and extract streamID from reply
2018-01-11 22:00:59 +00:00
streamId, err := initDecoder(c.decoder)
if err != nil {
2018-01-12 17:14:41 +00:00
return errors.New("cannot init decoder " + err.Error())
2018-01-11 21:15:54 +00:00
}
2018-01-12 17:14:41 +00:00
// 3. Authentication
if _, err := fmt.Fprintf(conn, "<handshake>%s</handshake>", c.handshake(streamId)); err != nil {
return errors.New("cannot send handshake " + err.Error())
2018-01-11 22:00:59 +00:00
}
2018-01-12 17:14:41 +00:00
// 4. Check server response for authentication
2018-01-13 17:50:17 +00:00
val, err := next(c.decoder)
2018-01-11 22:00:59 +00:00
if err != nil {
return err
}
switch v := val.(type) {
case *StreamError:
2018-01-12 17:14:41 +00:00
return errors.New("handshake failed " + v.Error.Local)
case *Handshake:
2018-01-12 17:14:41 +00:00
return nil
2018-01-11 22:00:59 +00:00
default:
2018-01-13 17:50:17 +00:00
return errors.New("unexpected packet, got " + v.Name())
2018-01-11 22:00:59 +00:00
}
2018-01-12 17:14:41 +00:00
panic("unreachable")
}
2018-01-11 22:00:59 +00:00
2018-01-12 18:08:47 +00:00
// ReadPacket reads next incoming XMPP packet
2018-01-13 17:50:17 +00:00
func (c *Component) ReadPacket() (Packet, error) {
2018-01-26 08:55:39 +00:00
// TODO use defined interface Packet
2018-01-12 18:08:47 +00:00
return next(c.decoder)
}
2018-01-26 08:55:39 +00:00
// Send marshalls XMPP stanza and sends it to the server.
2018-01-17 17:47:34 +00:00
func (c *Component) Send(packet Packet) error {
data, err := xml.Marshal(packet)
if err != nil {
return errors.New("cannot marshal packet " + err.Error())
}
if _, err := fmt.Fprintf(c.conn, string(data)); err != nil {
return errors.New("cannot send packet " + err.Error())
}
return nil
}
2018-01-26 08:55:39 +00:00
// SendRaw sends an XMPP stanza as a string to the server.
// It can be invalid XML or XMPP content. In that case, the server will
// disconnect the component. It is up to the user of this method to
// carefully craft the XML content to produce valid XMPP.
func (c *Component) SendRaw(packet string) error {
fmt.Fprintf(c.conn, packet) // TODO handle errors
return nil
}
2018-01-25 22:16:55 +00:00
// handshake generates an authentication token based on StreamID and shared secret.
func (c *Component) handshake(streamId string) string {
// 1. Concatenate the Stream ID received from the server with the shared secret.
concatStr := streamId + c.Secret
// 2. Hash the concatenated string according to the SHA1 algorithm, i.e., SHA1( concat (sid, password)).
h := sha1.New()
h.Write([]byte(concatStr))
hash := h.Sum(nil)
// 3. Ensure that the hash output is in hexadecimal format, not binary or base64.
// 4. Convert the hash output to all lowercase characters.
encodedStr := hex.EncodeToString(hash)
return encodedStr
}
2018-01-12 17:14:41 +00:00
// ============================================================================
2018-01-25 22:16:55 +00:00
// Handshake Stanza
2018-01-12 17:14:41 +00:00
2018-01-25 22:16:55 +00:00
// Handshake is a stanza used by XMPP components to authenticate on XMPP
// component port.
2018-01-12 17:14:41 +00:00
type Handshake struct {
XMLName xml.Name `xml:"jabber:component:accept handshake"`
2018-01-25 22:16:55 +00:00
// TODO Add handshake value with test for proper serialization
// Value string `xml:",innerxml"`
2018-01-11 21:15:54 +00:00
}
2018-01-13 17:50:17 +00:00
func (Handshake) Name() string {
return "component:handshake"
}
2018-01-25 22:16:55 +00:00
// Handshake decoding wrapper
2018-01-13 17:50:17 +00:00
type handshakeDecoder struct{}
var handshake handshakeDecoder
func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
var packet Handshake
err := p.DecodeElement(&packet, &se)
return packet, err
}