From 9dd92e124738bea45d60514e3aa597d7f999d907 Mon Sep 17 00:00:00 2001 From: Egor Kovetskiy Date: Fri, 10 Apr 2015 15:30:57 +0600 Subject: [PATCH 1/8] fix eternal cycle with malicious xml packet --- xmpp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmpp.go b/xmpp.go index 889b68b..28991f2 100644 --- a/xmpp.go +++ b/xmpp.go @@ -685,7 +685,7 @@ type clientError struct { func nextStart(p *xml.Decoder) (xml.StartElement, error) { for { t, err := p.Token() - if err != nil && err != io.EOF { + if err != nil && err != io.EOF || t == nil { return xml.StartElement{}, err } switch t := t.(type) { From e8c25dcffedb38bdb46a300c62abbd30d1117222 Mon Sep 17 00:00:00 2001 From: James Andariese Date: Sun, 12 Apr 2015 22:12:16 -0700 Subject: [PATCH 2/8] attempt anonymous only when logging in without JID and password --- _example/example.go | 4 +- xmpp.go | 176 ++++++++++++++++++++++++-------------------- 2 files changed, 101 insertions(+), 79 deletions(-) diff --git a/_example/example.go b/_example/example.go index 9c01d45..9d8d707 100644 --- a/_example/example.go +++ b/_example/example.go @@ -32,7 +32,9 @@ func main() { } flag.Parse() if *username == "" || *password == "" { - flag.Usage() + if *debug { + fmt.Fprintf(os.Stderr, "no username or password were given; attempting ANONYMOUS auth\n") + } } if !*notls { diff --git a/xmpp.go b/xmpp.go index 28991f2..60d65b2 100644 --- a/xmpp.go +++ b/xmpp.go @@ -44,6 +44,10 @@ const ( // Default TLS configuration options var DefaultConfig tls.Config +func init() { + DefaultConfig.InsecureSkipVerify = true +} + // Cookie is a unique XMPP session identifier type Cookie uint64 @@ -272,11 +276,14 @@ func (c *Client) init(o *Options) error { c.p = xml.NewDecoder(c.conn) } + var domain string a := strings.SplitN(o.User, "@", 2) - if len(a) != 2 { - return errors.New("xmpp: invalid username (want user@domain): " + o.User) - } - domain := a[1] + if len(o.User) > 0 { + if len(a) != 2 { + return errors.New("xmpp: invalid username (want user@domain): " + o.User) + } + domain = a[1] + } // Otherwise, we'll be attempting ANONYMOUS // Declare intent to be a jabber client and gather stream features. f, err := c.startStream(o, domain) @@ -289,88 +296,101 @@ func (c *Client) init(o *Options) error { return err } - // Even digest forms of authentication are unsafe if we do not know that the host - // we are talking to is the actual server, and not a man in the middle playing - // proxy. - if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth { - return errors.New("refusing to authenticate over unencrypted TCP connection") - } - - 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. - 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) - break - } - if m == "DIGEST-MD5" { - mechanism = m - // Digest-MD5 authentication - fmt.Fprintf(c.conn, "\n", nsSASL) - var ch saslChallenge - if err = c.p.DecodeElement(&ch, nil); err != nil { - return errors.New("unmarshal : " + err.Error()) + if o.User == "" && o.Password == "" { + found_anonymous := false + for _, m := range f.Mechanisms.Mechanism { + if m == "ANONYMOUS" { + fmt.Fprintf(c.conn, "\n", nsSASL) + found_anonymous = true + break } - b, err := base64.StdEncoding.DecodeString(string(ch)) - if err != nil { - return err + } + if !found_anonymous { + return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified") + } + } else { + // Even digest forms of authentication are unsafe if we do not know that the host + // we are talking to is the actual server, and not a man in the middle playing + // proxy. + if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth { + return errors.New("refusing to authenticate over unencrypted TCP connection") + } + + mechanism := "" + for _, m := range f.Mechanisms.Mechanism { + if m == "ANONYMOUS" { + mechanism = m + fmt.Fprintf(c.conn, "\n", nsSASL) + break } - tokens := map[string]string{} - for _, token := range strings.Split(string(b), ",") { - kv := strings.SplitN(strings.TrimSpace(token), "=", 2) - if len(kv) == 2 { - if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { - kv[1] = kv[1][1 : len(kv[1])-1] - } - tokens[kv[0]] = kv[1] + + 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. + 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) + break + } + if m == "DIGEST-MD5" { + mechanism = m + // Digest-MD5 authentication + fmt.Fprintf(c.conn, "\n", nsSASL) + var ch saslChallenge + if err = c.p.DecodeElement(&ch, nil); err != nil { + return errors.New("unmarshal : " + err.Error()) } - } - realm, _ := tokens["realm"] - nonce, _ := tokens["nonce"] - qop, _ := tokens["qop"] - charset, _ := tokens["charset"] - cnonceStr := cnonce() - 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 + b, err := base64.StdEncoding.DecodeString(string(ch)) + if err != nil { + return err + } + tokens := map[string]string{} + for _, token := range strings.Split(string(b), ",") { + kv := strings.SplitN(strings.TrimSpace(token), "=", 2) + if len(kv) == 2 { + if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { + kv[1] = kv[1][1 : len(kv[1])-1] + } + tokens[kv[0]] = kv[1] + } + } + realm, _ := tokens["realm"] + nonce, _ := tokens["nonce"] + qop, _ := tokens["qop"] + charset, _ := tokens["charset"] + cnonceStr := cnonce() + 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 - fmt.Fprintf(c.conn, "%s\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) + fmt.Fprintf(c.conn, "%s\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) - var rspauth saslRspAuth - if err = c.p.DecodeElement(&rspauth, nil); err != nil { - return errors.New("unmarshal : " + err.Error()) + var rspauth saslRspAuth + if err = c.p.DecodeElement(&rspauth, nil); err != nil { + return errors.New("unmarshal : " + err.Error()) + } + b, err = base64.StdEncoding.DecodeString(string(rspauth)) + if err != nil { + return err + } + fmt.Fprintf(c.conn, "\n", nsSASL) + break } - b, err = base64.StdEncoding.DecodeString(string(rspauth)) - if err != nil { - return err - } - fmt.Fprintf(c.conn, "\n", nsSASL) - break + } + if mechanism == "" { + return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) } } - if mechanism == "" { - return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) - } - // Next message should be either success or failure. name, val, err := next(c.p) if err != nil { From a1c1069091ad699a9632f52f2f9b0d46075404c4 Mon Sep 17 00:00:00 2001 From: James Andariese Date: Sun, 12 Apr 2015 22:18:06 -0700 Subject: [PATCH 3/8] if username or password are specified, don't assume anonymous in example.go --- _example/example.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_example/example.go b/_example/example.go index 9d8d707..136af5a 100644 --- a/_example/example.go +++ b/_example/example.go @@ -32,8 +32,10 @@ func main() { } flag.Parse() if *username == "" || *password == "" { - if *debug { + if *debug && *username == "" && *password == "" { fmt.Fprintf(os.Stderr, "no username or password were given; attempting ANONYMOUS auth\n") + } else if *username != "" || *password != "" { + flag.Usage() } } From 6c1f4b23f80c8b01475ceea781f068028f41745b Mon Sep 17 00:00:00 2001 From: James Andariese Date: Sun, 12 Apr 2015 22:28:30 -0700 Subject: [PATCH 4/8] follow up from comment from mattn s/found_anonymous/foundAnonymous/g --- xmpp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xmpp.go b/xmpp.go index 60d65b2..d9bbaa7 100644 --- a/xmpp.go +++ b/xmpp.go @@ -297,15 +297,15 @@ func (c *Client) init(o *Options) error { } if o.User == "" && o.Password == "" { - found_anonymous := false + foundAnonymous := false for _, m := range f.Mechanisms.Mechanism { if m == "ANONYMOUS" { fmt.Fprintf(c.conn, "\n", nsSASL) - found_anonymous = true + foundAnonymous = true break } } - if !found_anonymous { + if !foundAnonymous { return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified") } } else { From 9c349bcc3f70ed52527d3b8e23d2af92d0985cfa Mon Sep 17 00:00:00 2001 From: James Andariese Date: Mon, 13 Apr 2015 07:50:54 -0700 Subject: [PATCH 5/8] Default change to InsecureSkipVerify removed Slipped through. This is definitely not a good default for most people. --- xmpp.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xmpp.go b/xmpp.go index d9bbaa7..5762e0c 100644 --- a/xmpp.go +++ b/xmpp.go @@ -44,10 +44,6 @@ const ( // Default TLS configuration options var DefaultConfig tls.Config -func init() { - DefaultConfig.InsecureSkipVerify = true -} - // Cookie is a unique XMPP session identifier type Cookie uint64 From 874e70e091173c5534dce690802d9008666d7f89 Mon Sep 17 00:00:00 2001 From: Like-all Date: Mon, 13 Apr 2015 23:41:49 +0300 Subject: [PATCH 6/8] Subscription handling --- xmpp_subscription.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 xmpp_subscription.go diff --git a/xmpp_subscription.go b/xmpp_subscription.go new file mode 100644 index 0000000..b714c12 --- /dev/null +++ b/xmpp_subscription.go @@ -0,0 +1,20 @@ +package xmpp + +import ( + "fmt" +) + +func (c* Client) ApproveSubscription(jid string) { + fmt.Fprintf(c.conn, "", + xmlEscape(jid)) +} + +func (c* Client) RevokeSubscription(jid string) { + fmt.Fprintf(c.conn, "", + xmlEscape(jid)) +} + +func (c* Client) RequestSubscription(jid string) { + fmt.Fprintf(c.conn, "", + xmlEscape(jid)) +} From 861872c8db5f2704fea124d819619e5221501e45 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 16 Apr 2015 20:30:36 +0900 Subject: [PATCH 7/8] Add Roster() --- xmpp.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xmpp.go b/xmpp.go index 28991f2..2012ce3 100644 --- a/xmpp.go +++ b/xmpp.go @@ -561,6 +561,12 @@ func (c *Client) SendHtml(chat Chat) (n int, err error) { xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text) } +// Roster asks for the chat roster. +func (c *Client) Roster() error { + fmt.Fprintf(c.conn, "\n", xmlEscape(c.jid)) + return nil +} + // RFC 3920 C.1 Streams name space type streamFeatures struct { XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"` From 0c0c98633ce0f293cbecd38e0d46452a0f449454 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 16 Apr 2015 20:35:08 +0900 Subject: [PATCH 8/8] handle clientQuery --- xmpp.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/xmpp.go b/xmpp.go index 2012ce3..0fc7b70 100644 --- a/xmpp.go +++ b/xmpp.go @@ -513,9 +513,18 @@ type Chat struct { Remote string Type string Text string + Roster Roster Other []string } +type Roster []Contact + +type Contact struct { + Remote string + Name string + Group []string +} + // Presence is an XMPP presence notification. type Presence struct { From string @@ -534,7 +543,13 @@ func (c *Client) Recv() (stanza interface{}, err error) { } switch v := val.(type) { case *clientMessage: - return Chat{v.From, v.Type, v.Body, v.Other}, nil + return Chat{Remote: v.From, Type: v.Type, Text: v.Body, Other: v.Other}, nil + case *clientQuery: + var r Roster + for _, item := range v.Item { + r = append(r, Contact{item.Jid, item.Name, item.Group}) + } + return Chat{Type: "roster", Roster: r}, nil case *clientPresence: return Presence{v.From, v.To, v.Type, v.Show}, nil } @@ -687,6 +702,18 @@ type clientError struct { Text string } +type clientQuery struct { + Item []rosterItem +} + +type rosterItem struct { + XMLName xml.Name `xml:"jabber:iq:roster item"` + Jid string `xml:",attr"` + Name string `xml:",attr"` + Subscription string `xml:",attr"` + Group []string +} + // Scan XML token stream to find next StartElement. func nextStart(p *xml.Decoder) (xml.StartElement, error) { for {