diff --git a/xmpp.go b/xmpp.go index ec4f7d1..e669c92 100644 --- a/xmpp.go +++ b/xmpp.go @@ -38,11 +38,13 @@ const ( 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" + nsSession = "urn:ietf:params:xml:ns:xmpp-session" ) +// Default TLS configuration options var DefaultConfig tls.Config +// Cookie is a unique XMPP session identifier type Cookie uint64 func getCookie() Cookie { @@ -53,6 +55,7 @@ func getCookie() Cookie { return Cookie(binary.LittleEndian.Uint64(buf[:])) } +// Client holds XMPP connection opitons type Client struct { conn net.Conn // connection to server jid string // Jabber ID for our connection @@ -211,6 +214,7 @@ func NewClient(host, user, passwd string, debug bool) (*Client, error) { return opts.NewClient() } +// NewClientNoTLS creates a new client without TLS func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) { opts := Options{ Host: host, @@ -223,16 +227,15 @@ func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) { return opts.NewClient() } +// Close closes the XMPP connection func (c *Client) Close() error { if c.conn != (*tls.Conn)(nil) { return c.conn.Close() - } else { - return nil } + return nil } -func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, - authenticate, digestUri, nonceCountStr string) string { +func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string { h := func(text string) []byte { h := md5.New() h.Write([]byte(text)) @@ -245,12 +248,9 @@ func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, 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)))) + 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 } @@ -265,10 +265,11 @@ func cnonce() string { } func (c *Client) init(o *Options) error { - 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}) + // For debugging: the following causes the plaintext of the connection to be duplicated to stderr. + c.p = xml.NewDecoder(tee{c.conn, os.Stderr}) + } else { + c.p = xml.NewDecoder(c.conn) } a := strings.SplitN(o.User, "@", 2) @@ -284,7 +285,7 @@ func (c *Client) init(o *Options) error { } // If the server requires we STARTTLS, attempt to do so. - if f, err = c.startTlsIfRequired(f, o, domain); err != nil { + if f, err = c.startTLSIfRequired(f, o, domain); err != nil { return err } @@ -316,15 +317,13 @@ func (c *Client) init(o *Options) error { raw := "\x00" + user + "\x00" + o.Password enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) base64.StdEncoding.Encode(enc, []byte(raw)) - fmt.Fprintf(c.conn, "%s\n", - nsSASL, enc) + fmt.Fprintf(c.conn, "%s\n", nsSASL, enc) break } if m == "DIGEST-MD5" { mechanism = m // Digest-MD5 authentication - fmt.Fprintf(c.conn, "\n", - nsSASL) + fmt.Fprintf(c.conn, "\n", nsSASL) var ch saslChallenge if err = c.p.DecodeElement(&ch, nil); err != nil { return errors.New("unmarshal : " + err.Error()) @@ -348,10 +347,11 @@ func (c *Client) init(o *Options) error { qop, _ := tokens["qop"] charset, _ := tokens["charset"] cnonceStr := cnonce() - digestUri := "xmpp/" + domain + 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 + 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))) @@ -368,7 +368,7 @@ func (c *Client) init(o *Options) error { } } if mechanism == "" { - return errors.New(fmt.Sprintf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)) + return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) } // Next message should be either success or failure. @@ -392,7 +392,7 @@ func (c *Client) init(o *Options) error { return err } - //Generate uniq cookie + // Generate a uniqe cookie cookie := getCookie() // Send IQ message asking to bind to the local user name. @@ -412,7 +412,7 @@ func (c *Client) init(o *Options) error { if o.Session { //if server support session, open it - fmt.Fprintf(c.conn, "", xmlEscape(domain), cookie, NsSession) + fmt.Fprintf(c.conn, "", xmlEscape(domain), cookie, nsSession) } // We're connected and can now receive and send messages. @@ -423,7 +423,7 @@ func (c *Client) init(o *Options) error { // startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake. // f will be updated if the handshake completes, as the new stream's features are typically different from the original. -func (c *Client) startTlsIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) { +func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) { // whether we start tls is a matter of opinion: the server's and the user's. switch { case f.StartTLS == nil: @@ -465,12 +465,13 @@ func (c *Client) startTlsIfRequired(f *streamFeatures, o *Options, domain string } // startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has -// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stdout. The features advertised by the server +// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr. The features advertised by the server // will be returned. func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) { - c.p = xml.NewDecoder(c.conn) if o.Debug { - c.p = xml.NewDecoder(tee{c.conn, os.Stdout}) + c.p = xml.NewDecoder(tee{c.conn, os.Stderr}) + } else { + c.p = xml.NewDecoder(c.conn) } _, err := fmt.Fprintf(c.conn, "\n"+ @@ -508,6 +509,7 @@ func (c *Client) IsEncrypted() bool { return ok } +// Chat is an incoming or outgoing XMPP chat message type Chat struct { Remote string Type string @@ -515,6 +517,7 @@ type Chat struct { Other []string } +// Presence is an XMPP presence message type Presence struct { From string To string @@ -546,7 +549,7 @@ func (c *Client) Send(chat Chat) (n int, err error) { xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) } -// Send origin +// SendOrg sends the original text without being wrapped in an XMPP message stanza. func (c *Client) SendOrg(org string) (n int, err error) { return fmt.Fprint(c.conn, org) } @@ -625,7 +628,7 @@ type bindBind struct { type clientMessage struct { XMLName xml.Name `xml:"jabber:client message"` From string `xml:"from,attr"` - Id string `xml:"id,attr"` + ID string `xml:"id,attr"` To string `xml:"to,attr"` Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal @@ -647,7 +650,7 @@ type clientText struct { type clientPresence struct { XMLName xml.Name `xml:"jabber:client presence"` From string `xml:"from,attr"` - Id string `xml:"id,attr"` + ID string `xml:"id,attr"` To string `xml:"to,attr"` Type string `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed Lang string `xml:"lang,attr"` @@ -661,7 +664,7 @@ type clientPresence struct { type clientIQ struct { // info/query XMLName xml.Name `xml:"jabber:client iq"` From string `xml:",attr"` - Id string `xml:",attr"` + ID string `xml:",attr"` To string `xml:",attr"` Type string `xml:",attr"` // error, get, result, set Error clientError