Merge pull request #1 from mattn/master

catch up to upstream mattn/go-xmpp
This commit is contained in:
Scott Dunlop 2014-10-28 14:44:33 -07:00
commit ebd519cbfe
4 changed files with 134 additions and 29 deletions

4
README.md Normal file
View File

@ -0,0 +1,4 @@
go-xmpp
=======
go xmpp library (original was written by russ cox )

View File

@ -1,8 +1,9 @@
package main package main
import ( import (
"github.com/mattn/go-xmpp" "crypto/tls"
"github.com/mattn/go-gtk/gtk" "github.com/mattn/go-gtk/gtk"
"github.com/mattn/go-xmpp"
"log" "log"
"os" "os"
"strings" "strings"
@ -56,7 +57,7 @@ func main() {
dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK) dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK)
dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
dialog.SetDefaultResponse(int(gtk.RESPONSE_OK)) dialog.SetDefaultResponse(gtk.RESPONSE_OK)
dialog.SetTransientFor(window) dialog.SetTransientFor(window)
dialog.ShowAll() dialog.ShowAll()
res := dialog.Run() res := dialog.Run()
@ -67,7 +68,12 @@ func main() {
os.Exit(0) os.Exit(0)
} }
talk, err := xmpp.NewClient("talk.google.com:443", username_, password_) xmpp.DefaultConfig = tls.Config{
ServerName: "talk.google.com",
InsecureSkipVerify: false,
}
talk, err := xmpp.NewClient("talk.google.com:443", username_, password_, false)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -2,8 +2,9 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "crypto/tls"
"flag" "flag"
"fmt"
"github.com/mattn/go-xmpp" "github.com/mattn/go-xmpp"
"log" "log"
"os" "os"
@ -13,7 +14,15 @@ import (
var server = flag.String("server", "talk.google.com:443", "server") var server = flag.String("server", "talk.google.com:443", "server")
var username = flag.String("username", "", "username") var username = flag.String("username", "", "username")
var password = flag.String("password", "", "password") var password = flag.String("password", "", "password")
var status = flag.String("status", "xa", "status")
var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message")
var notls = flag.Bool("notls", false, "No TLS") var notls = flag.Bool("notls", false, "No TLS")
var debug = flag.Bool("debug", false, "debug output")
var session = flag.Bool("session", false, "use server session")
func serverName(host string) string {
return strings.Split(host, ":")[0]
}
func main() { func main() {
flag.Usage = func() { flag.Usage = func() {
@ -26,13 +35,27 @@ func main() {
flag.Usage() flag.Usage()
} }
if !*notls {
xmpp.DefaultConfig = tls.Config{
ServerName: serverName(*server),
InsecureSkipVerify: false,
}
}
var talk *xmpp.Client var talk *xmpp.Client
var err error var err error
if *notls { options := xmpp.Options{Host: *server,
talk, err = xmpp.NewClientNoTLS(*server, *username, *password) User: *username,
} else { Password: *password,
talk, err = xmpp.NewClient(*server, *username, *password) NoTLS: *notls,
Debug: *debug,
Session: *session,
Status: *status,
StatusMessage: *statusMessage,
} }
talk, err = options.NewClient()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

102
xmpp.go
View File

@ -19,11 +19,11 @@ import (
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/binary"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"math/big" "math/big"
"net" "net"
"net/http" "net/http"
@ -38,10 +38,21 @@ const (
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
nsBind = "urn:ietf:params:xml:ns:xmpp-bind" nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
nsClient = "jabber:client" nsClient = "jabber:client"
NsSession = "urn:ietf:params:xml:ns:xmpp-session"
) )
var DefaultConfig tls.Config var DefaultConfig tls.Config
type Cookie uint64
func getCookie() Cookie {
var buf [8]byte
if _, err := rand.Reader.Read(buf[:]); err != nil {
panic("Failed to read random bytes: " + err.Error())
}
return Cookie(binary.LittleEndian.Uint64(buf[:]))
}
type Client struct { type Client struct {
conn net.Conn // connection to server conn net.Conn // connection to server
jid string // Jabber ID for our connection jid string // Jabber ID for our connection
@ -112,9 +123,24 @@ type Options struct {
// from the server. Use "" to let the server generate one for your client. // from the server. Use "" to let the server generate one for your client.
Resource string Resource string
// TLS Config
TLSConfig *tls.Config
// NoTLS disables TLS and specifies that a plain old unencrypted TCP connection should // NoTLS disables TLS and specifies that a plain old unencrypted TCP connection should
// be used. // be used.
NoTLS bool NoTLS bool
// Debug output
Debug bool
// Use server sessions
Session bool
// Presence Status
Status string
// Status message
StatusMessage string
} }
// NewClient establishes a new Client connection based on a set of Options. // NewClient establishes a new Client connection based on a set of Options.
@ -129,7 +155,12 @@ func (o Options) NewClient() (*Client, error) {
if o.NoTLS { if o.NoTLS {
client.conn = c client.conn = c
} else { } else {
tlsconn := tls.Client(c, &DefaultConfig) var tlsconn *tls.Conn
if o.TLSConfig != nil {
tlsconn = tls.Client(c, o.TLSConfig)
} else {
tlsconn = tls.Client(c, &DefaultConfig)
}
if err = tlsconn.Handshake(); err != nil { if err = tlsconn.Handshake(); err != nil {
return nil, err return nil, err
} }
@ -153,21 +184,25 @@ func (o Options) NewClient() (*Client, error) {
// NewClient creates a new connection to a host given as "hostname" or "hostname:port". // NewClient creates a new connection to a host given as "hostname" or "hostname:port".
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID. // If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
// Default the port to 5222. // Default the port to 5222.
func NewClient(host, user, passwd string) (*Client, error) { func NewClient(host, user, passwd string, debug bool) (*Client, error) {
opts := Options{ opts := Options{
Host: host, Host: host,
User: user, User: user,
Password: passwd, Password: passwd,
Debug: debug,
Session: false,
} }
return opts.NewClient() return opts.NewClient()
} }
func NewClientNoTLS(host, user, passwd string) (*Client, error) { func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
opts := Options{ opts := Options{
Host: host, Host: host,
User: user, User: user,
Password: passwd, Password: passwd,
NoTLS: true, NoTLS: true,
Debug: debug,
Session: false,
} }
return opts.NewClient() return opts.NewClient()
} }
@ -210,15 +245,16 @@ func cnonce() string {
} }
func (c *Client) init(o *Options) error { func (c *Client) init(o *Options) error {
// For debugging: the following causes the plaintext of the connection to be duplicated to stdout.
//c.p = xml.NewDecoder(tee{c.conn, os.Stdout})
c.p = xml.NewDecoder(c.conn) c.p = xml.NewDecoder(c.conn)
// For debugging: the following causes the plaintext of the connection to be duplicated to stdout.
if o.Debug {
c.p = xml.NewDecoder(tee{c.conn, os.Stdout})
}
a := strings.SplitN(o.User, "@", 2) a := strings.SplitN(o.User, "@", 2)
if len(a) != 2 { if len(a) != 2 {
return errors.New("xmpp: invalid username (want user@domain): " + o.User) return errors.New("xmpp: invalid username (want user@domain): " + o.User)
} }
user := a[0]
domain := a[1] domain := a[1]
// Declare intent to be a jabber client. // Declare intent to be a jabber client.
@ -245,6 +281,19 @@ func (c *Client) init(o *Options) error {
} }
mechanism := "" mechanism := ""
for _, m := range f.Mechanisms.Mechanism { for _, m := range f.Mechanisms.Mechanism {
if m == "ANONYMOUS" {
mechanism = m
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
break
}
a := strings.SplitN(o.User, "@", 2)
if len(a) != 2 {
return errors.New("xmpp: invalid username (want user@domain): " + o.User)
}
user := a[0]
domain := a[1]
if m == "PLAIN" { if m == "PLAIN" {
mechanism = m mechanism = m
// Plain authentication: send base64-encoded \x00 user \x00 password. // Plain authentication: send base64-encoded \x00 user \x00 password.
@ -286,7 +335,8 @@ func (c *Client) init(o *Options) error {
digestUri := "xmpp/" + domain digestUri := "xmpp/" + domain
nonceCount := fmt.Sprintf("%08x", 1) nonceCount := fmt.Sprintf("%08x", 1)
digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestUri, nonceCount) digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestUri, nonceCount)
message := "username=" + user + ", realm=" + realm + ", nonce=" + nonce + ", cnonce=" + cnonceStr + ", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=" + digestUri + ", response=" + digest + ", charset=" + charset message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr + "\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestUri + "\", response=" + digest + ", charset=" + charset
fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
var rspauth saslRspAuth var rspauth saslRspAuth
@ -307,6 +357,9 @@ func (c *Client) init(o *Options) error {
// Next message should be either success or failure. // Next message should be either success or failure.
name, val, err := next(c.p) name, val, err := next(c.p)
if err != nil {
return err
}
switch v := val.(type) { switch v := val.(type) {
case *saslSuccess: case *saslSuccess:
case *saslFailure: case *saslFailure:
@ -333,14 +386,17 @@ func (c *Client) init(o *Options) error {
} }
if err = c.p.DecodeElement(&f, nil); err != nil { if err = c.p.DecodeElement(&f, nil); err != nil {
// TODO: often stream stop. // TODO: often stream stop.
//return os.NewError("unmarshal <features>: " + err.String()) //return errors.New("unmarshal <features>: " + err.Error())
} }
//Generate uniq cookie
cookie := getCookie()
// Send IQ message asking to bind to the local user name. // Send IQ message asking to bind to the local user name.
if o.Resource == "" { if o.Resource == "" {
fmt.Fprintf(c.conn, "<iq type='set' id='x'><bind xmlns='%s'></bind></iq>\n", nsBind) fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
} else { } else {
fmt.Fprintf(c.conn, "<iq type='set' id='x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", nsBind, o.Resource) fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
} }
var iq clientIQ var iq clientIQ
if err = c.p.DecodeElement(&iq, nil); err != nil { if err = c.p.DecodeElement(&iq, nil); err != nil {
@ -351,8 +407,14 @@ func (c *Client) init(o *Options) error {
} }
c.jid = iq.Bind.Jid // our local id c.jid = iq.Bind.Jid // our local id
if o.Session {
//if server support session, open it
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, NsSession)
}
// We're connected and can now receive and send messages. // We're connected and can now receive and send messages.
fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>xa</show><status>I for one welcome our new codebot overlords.</status></presence>") fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)
return nil return nil
} }
@ -360,6 +422,7 @@ type Chat struct {
Remote string Remote string
Type string Type string
Text string Text string
Other []string
} }
type Presence struct { type Presence struct {
@ -378,7 +441,7 @@ func (c *Client) Recv() (event interface{}, err error) {
} }
switch v := val.(type) { switch v := val.(type) {
case *clientMessage: case *clientMessage:
return Chat{v.From, v.Type, v.Body}, nil return Chat{v.From, v.Type, v.Body, v.Other}, nil
case *clientPresence: case *clientPresence:
return Presence{v.From, v.To, v.Type, v.Show}, nil return Presence{v.From, v.To, v.Type, v.Show}, nil
} }
@ -393,6 +456,11 @@ func (c *Client) Send(chat Chat) {
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
} }
// Send origin
func (c *Client) SendOrg(org string) {
fmt.Fprint(c.conn, org)
}
// RFC 3920 C.1 Streams name space // RFC 3920 C.1 Streams name space
type streamFeatures struct { type streamFeatures struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"` XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
@ -476,6 +544,9 @@ type clientMessage struct {
Subject string `xml:"subject"` Subject string `xml:"subject"`
Body string `xml:"body"` Body string `xml:"body"`
Thread string `xml:"thread"` Thread string `xml:"thread"`
// Any hasn't matched element
Other []string `xml:",any"`
} }
type clientText struct { type clientText struct {
@ -519,8 +590,8 @@ type clientError struct {
func nextStart(p *xml.Decoder) (xml.StartElement, error) { func nextStart(p *xml.Decoder) (xml.StartElement, error) {
for { for {
t, err := p.Token() t, err := p.Token()
if err != nil { if err != nil && err != io.EOF {
log.Fatal("token", err) return xml.StartElement{}, err
} }
switch t := t.(type) { switch t := t.(type) {
case xml.StartElement: case xml.StartElement:
@ -617,6 +688,7 @@ func (t tee) Read(p []byte) (n int, err error) {
n, err = t.r.Read(p) n, err = t.r.Read(p)
if n > 0 { if n > 0 {
t.w.Write(p[0:n]) t.w.Write(p[0:n])
t.w.Write([]byte("\n"))
} }
return return
} }