diff --git a/example/example-gui.go b/example/example-gui.go index f29bdf0..0787cfc 100644 --- a/example/example-gui.go +++ b/example/example-gui.go @@ -1,8 +1,8 @@ package main import ( - "github.com/mattn/go-xmpp" "github.com/mattn/go-gtk/gtk" + "github.com/mattn/go-xmpp" "log" "os" "strings" @@ -67,7 +67,7 @@ func main() { os.Exit(0) } - talk, err := xmpp.NewClient("talk.google.com:443", username_, password_) + 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 index 5702a74..3981192 100644 --- a/example/example.go +++ b/example/example.go @@ -2,18 +2,20 @@ package main import ( "bufio" - "fmt" "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 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 main() { flag.Usage = func() { @@ -28,11 +30,15 @@ func main() { 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} + + talk, err = options.NewClient() + if err != nil { log.Fatal(err) } diff --git a/xmpp.go b/xmpp.go index b5edcf1..00d86b5 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 @@ -115,6 +126,12 @@ type Options struct { // 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 } // NewClient establishes a new Client connection based on a set of Options. @@ -153,21 +170,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,9 +231,11 @@ 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 { @@ -245,6 +268,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 +322,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 +344,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 +373,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,6 +394,11 @@ 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.") return nil @@ -360,6 +408,7 @@ type Chat struct { Remote string Type string Text string + Other []string } type Presence struct { @@ -378,7 +427,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 +442,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 +530,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 +576,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 +674,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 }