// Deal with SASL authentication.packagexmppimport("crypto/md5""crypto/rand""encoding/base64""encoding/xml""fmt""math/big""regexp""strings")// Server is advertising auth mechanisms it supports. Choose one and// respond.// BUG(cjyar): Doesn't implement TLS/SASL EXTERNAL.func(cl*Client)chooseSasl(fe*Features){vardigestMd5boolfor_,m:=rangefe.Mechanisms.Mechanism{switchstrings.ToLower(m){case"digest-md5":digestMd5=true}}ifdigestMd5{auth:=&auth{XMLName:xml.Name{Space:NsSASL,Local:"auth"},Mechanism:"DIGEST-MD5"}cl.sendRaw<-auth}}// Server is responding to our auth request.func(cl*Client)handleSasl(srv*auth){switchstrings.ToLower(srv.XMLName.Local){case"challenge":b64:=base64.StdEncodingstr,err:=b64.DecodeString(srv.Chardata)iferr!=nil{cl.setError(fmt.Errorf("SASL: %v",err))return}srvMap:=parseSasl(string(str))ifcl.saslExpected==""{cl.saslDigest1(srvMap)}else{cl.saslDigest2(srvMap)}case"failure":cl.setError(fmt.Errorf("SASL authentication failed"))case"success":cl.setStatus(StatusAuthenticated)cl.Features=nilss:=&stream{To:cl.Jid.Domain(),Version:XMPPVersion}cl.sendRaw<-ss}}func(cl*Client)saslDigest1(srvMapmap[string]string){// Make sure it supports qop=authvarhasAuthboolfor_,qop:=rangestrings.Fields(srvMap["qop"]){ifqop=="auth"{hasAuth=true}}if!hasAuth{cl.setError(fmt.Errorf("Server doesn't support SASL auth"))return}// Pick a realm.varrealmstringifsrvMap["realm"]!=""{realm=strings.Fields(srvMap["realm"])[0]}passwd:=cl.passwordnonce:=srvMap["nonce"]digestUri:="xmpp/"+cl.Jid.Domain()nonceCount:=int32(1)nonceCountStr:=fmt.Sprintf("%08x",nonceCount)// Begin building the response. Username is// user@domain or just domain.varusernamestringifcl.Jid.Node()==""{username=cl.Jid.Domain()}else{username=cl.Jid.Node()}// Generate our own nonce from random data.randSize:=big.NewInt(0)randSize.Lsh(big.NewInt(1),64)cnonce,err:=rand.Int(rand.Reader,randSize)iferr!=nil{cl.setError(fmt.Errorf("SASL rand: %v",err))return}cnonceStr:=fmt.Sprintf("%016x",cnonce)/* Now encode the actual password response, as well as the * expected next challenge from the server. */response:=saslDigestResponse(username,realm,passwd,nonce,cnonceStr,"AUTHENTICATE",digestUri,nonceCountStr)next:=saslDigestResponse(username,realm,passwd,nonce,cnonceStr,"",digestUri,nonceCountStr)cl.saslExpected=next// Build the map which will be encoded.clMap:=make(map[string]string)clMap["realm"]=`"`+realm+`"`clMap["username"]=`"`+username+`"`clMap["nonce"]=`"`+nonce+`"`clMap["cnonce"]=`"`+cnonceStr+`"`clMap["nc"]=nonceCountStrclMap["qop"]="auth"clMap["digest-uri"]=`"`+digestUri+`"`clMap["response"]=responseifsrvMap["charset"]=="utf-8"{clMap["charset"]="utf-8"}// Encode the map and send it.clStr:=packSasl(clMap)b64:=base64.StdEncodingclObj:=&auth{XMLName:xml.Name{Space:NsSASL,Local:"response"},Chardata:b64.EncodeToString([]byte(clStr))}cl.sendRaw<-clObj}func(cl*Client)saslDigest2(srvMapmap[string]string){ifcl.saslExpected==srvMap["rspauth"]{clObj:=&auth{XMLName:xml.Name{Space:NsSASL,Local:"response"}}cl.sendRaw<-clObj}else{clObj:=&auth{XMLName:xml.Name{Space:NsSASL,Local:"failure"},Any:&Generic{XMLName:xml.Name{Space:NsSASL,Local:"abort"}}}cl.sendRaw<-clObj}}// Takes a string like `key1=value1,key2="value2"...` and returns a// key/value map.funcparseSasl(instring)map[string]string{re:=regexp.MustCompile(`([^=]+)="?([^",]+)"?,?`)strs:=re.FindAllStringSubmatch(in,-1)m:=make(map[string]string)for_,pair:=rangestrs{key:=strings.ToLower(string(pair[1]))value:=string(pair[2])m[key]=value}returnm}// Inverse of parseSasl().funcpackSasl(mmap[string]string)string{varterms[]stringforkey,value:=rangem{ifkey==""||value==""||value==`""`{continue}terms=append(terms,key+"="+value)}returnstrings.Join(terms,",")}// Computes the response string for digest authentication.funcsaslDigestResponse(username,realm,passwd,nonce,cnonceStr,authenticate,digestUri,nonceCountStrstring)string{h:=func(textstring)string{h:=md5.New()h.Write([]byte(text))returnstring(h.Sum(nil))}hex:=func(inputstring)string{returnfmt.Sprintf("%x",input)}kd:=func(secret,datastring)string{returnh(secret+":"+data)}a1:=h(username+":"+realm+":"+passwd)+":"+nonce+":"+cnonceStra2:=authenticate+":"+digestUriresponse:=hex(kd(hex(h(a1)),nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))returnresponse}