From e8c25dcffedb38bdb46a300c62abbd30d1117222 Mon Sep 17 00:00:00 2001 From: James Andariese Date: Sun, 12 Apr 2015 22:12:16 -0700 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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