From 5e57ac52f9ccd948ae51bb62db6aa4500fb79891 Mon Sep 17 00:00:00 2001 From: Scott Dunlop Date: Mon, 12 Aug 2013 15:33:50 -0700 Subject: [PATCH 1/2] added Options to NewClient, and Resource binding to Options --- xmpp.go | 76 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/xmpp.go b/xmpp.go index 1e7eec4..4fbeb63 100644 --- a/xmpp.go +++ b/xmpp.go @@ -43,10 +43,10 @@ const ( var DefaultConfig tls.Config type Client struct { - conn net.Conn // connection to server - jid string // Jabber ID for our connection + conn net.Conn // connection to server + jid string // Jabber ID for our connection domain string - p *xml.Decoder + p *xml.Decoder } func connect(host, user, passwd string) (net.Conn, error) { @@ -95,10 +95,17 @@ func connect(host, user, passwd string) (net.Conn, error) { return c, nil } +// Options are used to specify additional options for new clients, such as a Resource. +type Options struct { + // Resource specifies an XMPP client resource, like "bot", instead of accepting one + // from the server. Use "" to let the server generate one for your client. + Resource string +} + // 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, opts *Options) (*Client, error) { c, err := connect(host, user, passwd) if err != nil { return nil, err @@ -118,14 +125,14 @@ func NewClient(host, user, passwd string) (*Client, error) { client := new(Client) client.conn = tlsconn - if err := client.init(user, passwd); err != nil { + if err := client.init(user, passwd, opts); err != nil { client.Close() return nil, err } return client, nil } -func NewClientNoTLS(host, user, passwd string) (*Client, error) { +func NewClientNoTLS(host, user, passwd string, opts *Options) (*Client, error) { c, err := connect(host, user, passwd) if err != nil { return nil, err @@ -133,7 +140,7 @@ func NewClientNoTLS(host, user, passwd string) (*Client, error) { client := new(Client) client.conn = c - if err := client.init(user, passwd); err != nil { + if err := client.init(user, passwd, opts); err != nil { client.Close() return nil, err } @@ -145,26 +152,26 @@ func (c *Client) Close() error { } func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, - authenticate, digestUri, nonceCountStr string) string { - h := func(text string) []byte { - h := md5.New() - h.Write([]byte(text)) - return h.Sum(nil) - } - hex := func(bytes []byte) string { - return fmt.Sprintf("%x", bytes) - } - kd := func(secret, data string) []byte { - return h(secret + ":" + data) - } + authenticate, digestUri, nonceCountStr string) string { + h := func(text string) []byte { + h := md5.New() + h.Write([]byte(text)) + return h.Sum(nil) + } + hex := func(bytes []byte) string { + return fmt.Sprintf("%x", bytes) + } + kd := func(secret, data string) []byte { + return h(secret + ":" + data) + } - a1 := string(h(username+":"+realm+":"+passwd)) + ":" + - nonce + ":" + cnonceStr - a2 := authenticate + ":" + digestUri - response := hex(kd(hex(h(a1)), nonce+":"+ - nonceCountStr+":"+cnonceStr+":auth:"+ - hex(h(a2)))) - return response + a1 := string(h(username+":"+realm+":"+passwd)) + ":" + + nonce + ":" + cnonceStr + a2 := authenticate + ":" + digestUri + response := hex(kd(hex(h(a1)), nonce+":"+ + nonceCountStr+":"+cnonceStr+":auth:"+ + hex(h(a2)))) + return response } func cnonce() string { @@ -177,7 +184,7 @@ func cnonce() string { return fmt.Sprintf("%016x", cn) } -func (c *Client) init(user, passwd string) error { +func (c *Client) init(user, passwd string, opts *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) @@ -189,6 +196,11 @@ func (c *Client) init(user, passwd string) error { user = a[0] domain := a[1] + resource := "" + if opts != nil { + resource = opts.Resource + } + // Declare intent to be a jabber client. fmt.Fprintf(c.conn, "\n"+ "%s\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) var rspauth saslRspAuth @@ -305,7 +317,11 @@ func (c *Client) init(user, passwd string) error { } // Send IQ message asking to bind to the local user name. - fmt.Fprintf(c.conn, "\n", nsBind) + if resource == "" { + fmt.Fprintf(c.conn, "\n", nsBind) + } else { + fmt.Fprintf(c.conn, "%s\n", nsBind, resource) + } var iq clientIQ if err = c.p.DecodeElement(&iq, nil); err != nil { return errors.New("unmarshal : " + err.Error()) From 7c9260e5a0b2a4603bc1bf2f05e3350a5c022b3d Mon Sep 17 00:00:00 2001 From: Scott Dunlop Date: Mon, 12 Aug 2013 16:04:39 -0700 Subject: [PATCH 2/2] added the normal arguments to NewClient to Options and made that a central entrypoint for creating new clients --- xmpp.go | 118 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/xmpp.go b/xmpp.go index 4fbeb63..b5edcf1 100644 --- a/xmpp.go +++ b/xmpp.go @@ -97,54 +97,79 @@ func connect(host, user, passwd string) (net.Conn, error) { // Options are used to specify additional options for new clients, such as a Resource. type Options struct { + // Host specifies what host to connect to, as either "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. + Host string + + // User specifies what user to authenticate to the remote server. + User string + + // Password supplies the password to use for authentication with the remote server. + Password string + // Resource specifies an XMPP client resource, like "bot", instead of accepting one // from the server. Use "" to let the server generate one for your client. Resource string + + // NoTLS disables TLS and specifies that a plain old unencrypted TCP connection should + // be used. + NoTLS bool +} + +// NewClient establishes a new Client connection based on a set of Options. +func (o Options) NewClient() (*Client, error) { + host := o.Host + c, err := connect(host, o.User, o.Password) + if err != nil { + return nil, err + } + + client := new(Client) + if o.NoTLS { + client.conn = c + } else { + tlsconn := tls.Client(c, &DefaultConfig) + if err = tlsconn.Handshake(); err != nil { + return nil, err + } + if strings.LastIndex(o.Host, ":") > 0 { + host = host[:strings.LastIndex(o.Host, ":")] + } + if err = tlsconn.VerifyHostname(host); err != nil { + return nil, err + } + client.conn = tlsconn + } + + if err := client.init(&o); err != nil { + client.Close() + return nil, err + } + + return client, nil } // 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, opts *Options) (*Client, error) { - c, err := connect(host, user, passwd) - if err != nil { - return nil, err +func NewClient(host, user, passwd string) (*Client, error) { + opts := Options{ + Host: host, + User: user, + Password: passwd, } - - tlsconn := tls.Client(c, &DefaultConfig) - if err = tlsconn.Handshake(); err != nil { - return nil, err - } - - if strings.LastIndex(host, ":") > 0 { - host = host[:strings.LastIndex(host, ":")] - } - if err = tlsconn.VerifyHostname(host); err != nil { - return nil, err - } - - client := new(Client) - client.conn = tlsconn - if err := client.init(user, passwd, opts); err != nil { - client.Close() - return nil, err - } - return client, nil + return opts.NewClient() } -func NewClientNoTLS(host, user, passwd string, opts *Options) (*Client, error) { - c, err := connect(host, user, passwd) - if err != nil { - return nil, err +func NewClientNoTLS(host, user, passwd string) (*Client, error) { + opts := Options{ + Host: host, + User: user, + Password: passwd, + NoTLS: true, } - - client := new(Client) - client.conn = c - if err := client.init(user, passwd, opts); err != nil { - client.Close() - return nil, err - } - return client, nil + return opts.NewClient() } func (c *Client) Close() error { @@ -184,23 +209,18 @@ func cnonce() string { return fmt.Sprintf("%016x", cn) } -func (c *Client) init(user, passwd string, opts *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) - a := strings.SplitN(user, "@", 2) + a := strings.SplitN(o.User, "@", 2) if len(a) != 2 { - return errors.New("xmpp: invalid username (want user@domain): " + user) + return errors.New("xmpp: invalid username (want user@domain): " + o.User) } - user = a[0] + user := a[0] domain := a[1] - resource := "" - if opts != nil { - resource = opts.Resource - } - // Declare intent to be a jabber client. fmt.Fprintf(c.conn, "\n"+ "%s\n", @@ -265,7 +285,7 @@ func (c *Client) init(user, passwd string, opts *Options) error { cnonceStr := cnonce() digestUri := "xmpp/" + domain nonceCount := fmt.Sprintf("%08x", 1) - digest := saslDigestResponse(user, realm, passwd, 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 fmt.Fprintf(c.conn, "%s\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) @@ -317,10 +337,10 @@ func (c *Client) init(user, passwd string, opts *Options) error { } // Send IQ message asking to bind to the local user name. - if resource == "" { + if o.Resource == "" { fmt.Fprintf(c.conn, "\n", nsBind) } else { - fmt.Fprintf(c.conn, "%s\n", nsBind, resource) + fmt.Fprintf(c.conn, "%s\n", nsBind, o.Resource) } var iq clientIQ if err = c.p.DecodeElement(&iq, nil); err != nil {