diff --git a/README.md b/README.md
new file mode 100644
index 0000000..12e4a25
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+go-xmpp
+=======
+
+go xmpp library (original was written by russ cox )
diff --git a/example/example-gui.go b/_example/example-gui.go
similarity index 92%
rename from example/example-gui.go
rename to _example/example-gui.go
index f29bdf0..f8d3dbc 100644
--- a/example/example-gui.go
+++ b/_example/example-gui.go
@@ -1,8 +1,9 @@
package main
import (
- "github.com/mattn/go-xmpp"
+ "crypto/tls"
"github.com/mattn/go-gtk/gtk"
+ "github.com/mattn/go-xmpp"
"log"
"os"
"strings"
@@ -56,7 +57,7 @@ func main() {
dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK)
dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
- dialog.SetDefaultResponse(int(gtk.RESPONSE_OK))
+ dialog.SetDefaultResponse(gtk.RESPONSE_OK)
dialog.SetTransientFor(window)
dialog.ShowAll()
res := dialog.Run()
@@ -67,7 +68,12 @@ func main() {
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 {
log.Fatal(err)
}
diff --git a/example/example.go b/_example/example.go
similarity index 56%
rename from example/example.go
rename to _example/example.go
index 5702a74..9c01d45 100644
--- a/example/example.go
+++ b/_example/example.go
@@ -2,18 +2,27 @@ package main
import (
"bufio"
- "fmt"
+ "crypto/tls"
"flag"
+ "fmt"
"github.com/mattn/go-xmpp"
"log"
"os"
"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 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 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() {
flag.Usage = func() {
@@ -26,13 +35,27 @@ func main() {
flag.Usage()
}
+ if !*notls {
+ xmpp.DefaultConfig = tls.Config{
+ ServerName: serverName(*server),
+ InsecureSkipVerify: false,
+ }
+ }
+
var talk *xmpp.Client
var err error
- if *notls {
- talk, err = xmpp.NewClientNoTLS(*server, *username, *password)
- } else {
- talk, err = xmpp.NewClient(*server, *username, *password)
+ options := xmpp.Options{Host: *server,
+ User: *username,
+ Password: *password,
+ NoTLS: *notls,
+ Debug: *debug,
+ Session: *session,
+ Status: *status,
+ StatusMessage: *statusMessage,
}
+
+ talk, err = options.NewClient()
+
if err != nil {
log.Fatal(err)
}
diff --git a/xmpp.go b/xmpp.go
index b5edcf1..2b8a298 100644
--- a/xmpp.go
+++ b/xmpp.go
@@ -19,11 +19,11 @@ import (
"crypto/rand"
"crypto/tls"
"encoding/base64"
+ "encoding/binary"
"encoding/xml"
"errors"
"fmt"
"io"
- "log"
"math/big"
"net"
"net/http"
@@ -33,15 +33,26 @@ import (
)
const (
- nsStream = "http://etherx.jabber.org/streams"
- nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
- nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
- nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
- nsClient = "jabber:client"
+ nsStream = "http://etherx.jabber.org/streams"
+ nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
+ nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
+ nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
+ nsClient = "jabber:client"
+ NsSession = "urn:ietf:params:xml:ns:xmpp-session"
)
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 {
conn net.Conn // connection to server
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.
Resource string
+ // TLS Config
+ TLSConfig *tls.Config
+
// NoTLS disables TLS and specifies that a plain old unencrypted TCP connection should
// be used.
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.
@@ -129,7 +155,12 @@ func (o Options) NewClient() (*Client, error) {
if o.NoTLS {
client.conn = c
} 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 {
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".
// 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.
-func NewClient(host, user, passwd string) (*Client, error) {
+func NewClient(host, user, passwd string, debug bool) (*Client, error) {
opts := Options{
Host: host,
User: user,
Password: passwd,
+ Debug: debug,
+ Session: false,
}
return opts.NewClient()
}
-func NewClientNoTLS(host, user, passwd string) (*Client, error) {
+func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
opts := Options{
Host: host,
User: user,
Password: passwd,
NoTLS: true,
+ Debug: debug,
+ Session: false,
}
return opts.NewClient()
}
@@ -210,15 +245,16 @@ func cnonce() string {
}
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)
+ // 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)
if len(a) != 2 {
return errors.New("xmpp: invalid username (want user@domain): " + o.User)
}
- user := a[0]
domain := a[1]
// Declare intent to be a jabber client.
@@ -245,6 +281,19 @@ func (c *Client) init(o *Options) error {
}
mechanism := ""
for _, m := range f.Mechanisms.Mechanism {
+ if m == "ANONYMOUS" {
+ mechanism = m
+ fmt.Fprintf(c.conn, "\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" {
mechanism = m
// Plain authentication: send base64-encoded \x00 user \x00 password.
@@ -286,7 +335,8 @@ func (c *Client) init(o *Options) error {
digestUri := "xmpp/" + domain
nonceCount := fmt.Sprintf("%08x", 1)
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, "%s\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
var rspauth saslRspAuth
@@ -307,6 +357,9 @@ func (c *Client) init(o *Options) error {
// Next message should be either success or failure.
name, val, err := next(c.p)
+ if err != nil {
+ return err
+ }
switch v := val.(type) {
case *saslSuccess:
case *saslFailure:
@@ -333,14 +386,17 @@ func (c *Client) init(o *Options) error {
}
if err = c.p.DecodeElement(&f, nil); err != nil {
// TODO: often stream stop.
- //return os.NewError("unmarshal : " + err.String())
+ //return errors.New("unmarshal : " + err.Error())
}
+ //Generate uniq cookie
+ cookie := getCookie()
+
// Send IQ message asking to bind to the local user name.
if o.Resource == "" {
- fmt.Fprintf(c.conn, "\n", nsBind)
+ fmt.Fprintf(c.conn, "\n", cookie, nsBind)
} else {
- fmt.Fprintf(c.conn, "%s\n", nsBind, o.Resource)
+ fmt.Fprintf(c.conn, "%s\n", cookie, nsBind, o.Resource)
}
var iq clientIQ
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
+ if o.Session {
+ //if server support session, open it
+ fmt.Fprintf(c.conn, "", xmlEscape(domain), cookie, NsSession)
+ }
+
// We're connected and can now receive and send messages.
- fmt.Fprintf(c.conn, "xaI for one welcome our new codebot overlords.")
+ fmt.Fprintf(c.conn, "%s%s", o.Status, o.StatusMessage)
+
return nil
}
@@ -360,6 +422,7 @@ type Chat struct {
Remote string
Type string
Text string
+ Other []string
}
type Presence struct {
@@ -378,7 +441,7 @@ func (c *Client) Recv() (event interface{}, err error) {
}
switch v := val.(type) {
case *clientMessage:
- return Chat{v.From, v.Type, v.Body}, nil
+ return Chat{v.From, v.Type, v.Body, v.Other}, nil
case *clientPresence:
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))
}
+// Send origin
+func (c *Client) SendOrg(org string) {
+ fmt.Fprint(c.conn, org)
+}
+
// RFC 3920 C.1 Streams name space
type streamFeatures struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
@@ -476,6 +544,9 @@ type clientMessage struct {
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
+
+ // Any hasn't matched element
+ Other []string `xml:",any"`
}
type clientText struct {
@@ -519,8 +590,8 @@ type clientError struct {
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
for {
t, err := p.Token()
- if err != nil {
- log.Fatal("token", err)
+ if err != nil && err != io.EOF {
+ return xml.StartElement{}, err
}
switch t := t.(type) {
case xml.StartElement:
@@ -617,6 +688,7 @@ func (t tee) Read(p []byte) (n int, err error) {
n, err = t.r.Read(p)
if n > 0 {
t.w.Write(p[0:n])
+ t.w.Write([]byte("\n"))
}
return
}