Basic XMPP client with TLS and SASL.
This commit is contained in:
parent
a76c53f05f
commit
a793114fdf
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package xmpp
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
func saslEncodePlain(user, password string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte("\x00" + user + "\x00" + password))
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
Loading…
Reference in New Issue