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 {