1
0
Fork 0
go-xmpp/src/xmpp/client.go

236 lines
5.3 KiB
Go

package xmpp
import (
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"log"
)
// Config structure used to create a new XMPP client connection.
type ClientConfig struct {
// Don't upgrade the connection to TLS, even if the server supports it. If
// the server *requires* TLS then this option is ignored.
NoTLS bool
// Skip verification of the server's certificate chain. Probably only
// useful during development.
InsecureSkipVerify bool
}
// Create a client XMPP over the stream.
func NewClientXMPP(stream *Stream, jid JID, password string, config *ClientConfig) (*XMPP, error) {
for {
if err := startClient(stream, jid); err != nil {
return nil, err
}
// Read features.
f := new(features)
if err := stream.Decode(f); err != nil {
return nil, err
}
// TLS?
if f.StartTLS != nil && (f.StartTLS.Required != nil || !config.NoTLS) {
log.Println("Start TLS")
if err := startTLS(stream, config); err != nil {
return nil, err
}
continue // Restart
}
// Authentication
if f.Mechanisms != nil {
log.Println("Authenticating")
if err := authenticate(stream, f.Mechanisms.Mechanisms, jid.Node, password); err != nil {
return nil, err
}
continue // Restart
}
// Bind resource.
if f.Bind != nil {
log.Println("Binding resource.")
boundJID, err := bindResource(stream, jid)
if err != nil {
return nil, err
}
jid = boundJID
}
break
}
return newXMPP(jid, stream), nil
}
func startClient(stream *Stream, jid JID) error {
start := xml.StartElement{
xml.Name{"stream", "stream"},
[]xml.Attr{
xml.Attr{xml.Name{"", "xmlns"}, "jabber:client"},
xml.Attr{xml.Name{"xmlns", "stream"}, "http://etherx.jabber.org/streams"},
xml.Attr{xml.Name{"", "from"}, jid.Full()},
xml.Attr{xml.Name{"", "to"}, jid.Domain},
},
}
if err := stream.SendStart(&start); err != nil {
return err
}
if _, err := stream.Next(&xml.Name{nsStream, "stream"}); err != nil {
return err
}
return nil
}
func startTLS(stream *Stream, config *ClientConfig) error {
if err := stream.Send(&tlsStart{}); err != nil {
return err
}
p := tlsProceed{}
if err := stream.Decode(&p); err != nil {
return err
}
tlsConfig := tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}
return stream.UpgradeTLS(&tlsConfig)
}
type tlsStart struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
}
type tlsProceed struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
}
func authenticate(stream *Stream, mechanisms []string, user, password string) error {
log.Println("authenticate, mechanisms=", mechanisms)
if !stringSliceContains(mechanisms, "PLAIN") {
return errors.New("Only PLAIN supported for now")
}
return authenticatePlain(stream, user, password)
}
func authenticatePlain(stream *Stream, user, password string) error {
auth := saslAuth{Mechanism: "PLAIN", Message: saslEncodePlain(user, password)}
if err := stream.Send(&auth); err != nil {
return err
}
if se, err := stream.Next(nil); err != nil {
return err
} else {
if se.Name.Local == "failure" {
f := new(saslFailure)
stream.DecodeElement(f, se)
return errors.New(fmt.Sprintf("Authentication failed: %s", f.Reason.Local))
}
}
return nil
}
type saslAuth struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
Mechanism string `xml:"mechanism,attr"`
Message string `xml:",chardata"`
}
func bindResource(stream *Stream, jid JID) (JID, error) {
if jid.Resource == "" {
return bindResourceServer(stream)
}
return bindResourceClient(stream, jid)
}
func bindResourceClient(stream *Stream, jid JID) (JID, error) {
req := Iq{Id: "foo", Type: "set"}
req.PayloadEncode(bindIq{Resource: jid.Resource})
if err := stream.Send(req); err != nil {
return JID{}, err
}
resp := Iq{}
err := stream.Decode(&resp)
if err != nil {
return JID{}, err
}
bindResp := bindIq{}
resp.PayloadDecode(&bindResp)
boundJID, err := ParseJID(bindResp.JID)
return boundJID, nil
}
func bindResourceServer(stream *Stream) (JID, error) {
panic("bindResourceServer not implemented")
}
type bindIq struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
Resource string `xml:"resource,omitempty"`
JID string `xml:"jid,omitempty"`
}
func stringSliceContains(l []string, m string) bool {
for _, i := range l {
if i == m {
return true
}
}
return false
}
type features struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
StartTLS *tlsStartTLS `xml:"starttls"`
Mechanisms *mechanisms `xml:"mechanisms"`
Bind *bind `xml:"bind"`
}
type bind struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
Required *required `xml:"required"`
}
type mechanisms struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
Mechanisms []string `xml:"mechanism"`
}
type tlsStartTLS struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
Required *required `xml:"required"`
}
type required struct {}
type saslSuccess struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
}
type saslFailure struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
Reason xml.Name `xml:",any"`
}
// BUG(matt): Implement server-side resource binding.
// BUG(matt): Don't use "foo" as the <iq/> id during resource binding.