1
0
Fork 0

Basic XMPP client with TLS and SASL.

This commit is contained in:
Matt Goodall 2012-06-27 12:05:07 +01:00
parent a76c53f05f
commit a793114fdf
5 changed files with 316 additions and 0 deletions

27
demo.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"flag"
"log"
"xmpp"
)
func main() {
jid := flag.String("jid", "", "JID")
password := flag.String("pass", "", "Password")
flag.Parse()
jid2, err := xmpp.ParseJID(*jid)
if err != nil {
log.Fatal(err)
}
c, err := xmpp.NewClient(jid2, *password, &xmpp.ClientConfig{})
if err != nil {
log.Fatal(err)
}
log.Println(c)
select {}
}

145
src/xmpp/client.go Normal file
View File

@ -0,0 +1,145 @@
package xmpp
import (
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"log"
)
type Client struct {
JID JID
stream *Stream
}
type ClientConfig struct {
NoTLS bool
InsecureSkipVerify bool
}
func NewClient(jid JID, password string, config *ClientConfig) (*Client, error) {
stream, err := NewStream(jid.Domain + ":5222")
if err != nil {
return nil, err
}
if err := stream.Send("<?xml version='1.0'?>\n"); err != nil {
return nil, err
}
for {
// Send stream start.
s := fmt.Sprintf(
"<stream:stream from='%s' to='%s' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>",
jid,
jid.Domain)
if err := stream.Send(fmt.Sprintf(s)); err != nil {
return nil, err
}
// Receive stream start.
if _, err := stream.Next(&xml.Name{nsStream, "stream"}); 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) {
tlsConfig := tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}
if err := stream.UpgradeTLS(&tlsConfig); err != nil {
return nil, err
}
continue // Restart
}
// Authentication
if f.Mechanisms != nil {
log.Println("Authenticating")
if err := authenticate(stream, f.Mechanisms.Mechanisms, jid.Local, password); err != nil {
return nil, err
}
continue // Restart
}
break
}
return &Client{jid, stream}, nil
}
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 {
x := fmt.Sprintf(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>",
saslEncodePlain(user, password))
if err := stream.Send(x); 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
}
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"`
}
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 *tlsStartTLSRequired `xml:"required"`
}
type tlsStartTLSRequired 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"`
}

45
src/xmpp/jid.go Normal file
View File

@ -0,0 +1,45 @@
package xmpp
import (
"fmt"
"strings"
)
type JID struct {
Local string
Domain string
Resource string
}
func (jid JID) Bare() string {
if jid.Local == "" {
return jid.Domain
}
return fmt.Sprintf("%s@%s", jid.Local, jid.Domain)
}
func (jid JID) String() string {
if jid.Resource == "" {
return jid.Bare()
}
return fmt.Sprintf("%s@%s/%s", jid.Local, jid.Domain, jid.Resource)
}
func ParseJID(s string) (jid JID, err error) {
if parts := strings.SplitN(s, "/", 2); len(parts) == 1 {
s = parts[0]
} else {
s = parts[0]
jid.Resource = parts[1]
}
if parts := strings.SplitN(s, "@", 2); len(parts) != 2 {
jid.Domain = parts[0]
} else {
jid.Local = parts[0]
jid.Domain = parts[1]
}
return
}

7
src/xmpp/sasl.go Normal file
View File

@ -0,0 +1,7 @@
package xmpp
import "encoding/base64"
func saslEncodePlain(user, password string) string {
return base64.StdEncoding.EncodeToString([]byte("\x00" + user + "\x00" + password))
}

92
src/xmpp/stream.go Normal file
View File

@ -0,0 +1,92 @@
package xmpp
import (
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"log"
"net"
)
const (
nsStream = "http://etherx.jabber.org/streams"
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
)
type Stream struct {
conn net.Conn
dec *xml.Decoder
}
func NewStream(addr string) (*Stream, error) {
log.Println("Connecting to", addr)
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
dec := xml.NewDecoder(conn)
return &Stream{conn, dec}, nil
}
func (stream *Stream) UpgradeTLS(config *tls.Config) error {
log.Println("Upgrading to TLS")
if err := stream.Send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); err != nil {
return err
}
p := tlsProceed{}
if err := stream.Decode(&p); err != nil {
return err
}
conn := tls.Client(stream.conn, &tls.Config{InsecureSkipVerify: true})
if err := conn.Handshake(); err != nil {
return err
}
stream.conn = conn
stream.dec = xml.NewDecoder(stream.conn)
return nil
}
func (stream *Stream) Send(s string) error {
if _, err := stream.conn.Write([]byte(s)); err != nil {
return err
}
return nil
}
func (stream *Stream) Next(match *xml.Name) (*xml.StartElement, error) {
for {
t, err := stream.dec.Token()
if err != nil {
return nil, err
}
if e, ok := t.(xml.StartElement); ok {
if match != nil && e.Name != *match {
return nil, errors.New(fmt.Sprintf("Expected %s, got %s", *match, e.Name))
}
return &e, nil
}
}
panic("Unreachable")
}
func (stream *Stream) Decode(i interface{}) error {
return stream.dec.Decode(i)
}
func (stream *Stream) DecodeElement(i interface{}, se *xml.StartElement) error {
return stream.dec.DecodeElement(i, se)
}
type tlsProceed struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
}