forked from jshiffer/go-xmpp
Merge pull request #1 from mattn/master
catch up to upstream mattn/go-xmpp
This commit is contained in:
commit
ebd519cbfe
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
go-xmpp
|
||||||
|
=======
|
||||||
|
|
||||||
|
go xmpp library (original was written by russ cox )
|
@ -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)
|
||||||
}
|
}
|
@ -2,18 +2,27 @@ 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"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
112
xmpp.go
112
xmpp.go
@ -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"
|
||||||
@ -33,15 +33,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nsStream = "http://etherx.jabber.org/streams"
|
nsStream = "http://etherx.jabber.org/streams"
|
||||||
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
|
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
|
||||||
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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user