// This layer of the XMPP protocol reads XMLish structures and// responds to them. It negotiates TLS and authentication.packagexmppimport("encoding/xml")// Callback to handle a stanza with a particular id.typecallbackstruct{idstringffunc(Stanza)}// Receive XMPP stanzas from the client and send them on to the// remote. Don't allow the client to send us any stanzas until// negotiation has completed. This loop is paused until resource// binding is complete. Otherwise the app might inject something// inappropriate into our negotiations with the server. The control// channel controls this loop's activity.funcsendStream(sendXmlchan<-interface{},recvXmpp<-chanStanza,status<-chanStatus){deferclose(sendXml)varinput<-chanStanzafor{select{casestat:=<-status:switchstat{default:input=nilcaseStatusRunning:input=recvXmpp}casex,ok:=<-input:if!ok{return}ifx==nil{Info.Log("Refusing to send nil stanza")continue}sendXml<-x}}}// Receive XMLish structures, handle all the stream-related ones, and// send XMPP stanzas on to the client once the connection is running.func(cl*Client)recvStream(recvXml<-chaninterface{},sendXmppchan<-Stanza,status<-chanStatus){deferclose(sendXmpp)defercl.statmgr.close()handlers:=make(map[string]func(Stanza))doSend:=falsefor{select{casestat:=<-status:switchstat{default:doSend=falsecaseStatusRunning:doSend=true}caseh:=<-cl.handlers:handlers[h.id]=h.fcasex,ok:=<-recvXml:if!ok{return}switchobj:=x.(type){case*stream:// Do nothing.case*streamError:cl.handleStreamError(obj)case*Features:cl.handleFeatures(obj)case*starttls:cl.handleTls(obj)case*auth:cl.handleSasl(obj)caseStanza:id:=obj.GetHeader().Idifhandlers[id]!=nil{f:=handlers[id]delete(handlers,id)f(obj)}ifdoSend{sendXmpp<-obj}default:Warn.Logf("Unhandled non-stanza: %T %#v",x,x)}}}}func(cl*Client)handleStreamError(se*streamError){Info.Logf("Received stream error: %v",se)cl.setStatus(StatusShutdown)}func(cl*Client)handleFeatures(fe*Features){cl.Features=feiffe.Starttls!=nil{start:=&starttls{XMLName:xml.Name{Space:NsTLS,Local:"starttls"}}cl.sendXml<-startreturn}iflen(fe.Mechanisms.Mechanism)>0{cl.chooseSasl(fe)return}iffe.Bind!=nil{cl.bind()return}}func(cl*Client)handleTls(t*starttls){cl.layer1.startTls(&cl.tlsConfig)cl.setStatus(StatusConnectedTls)// Now re-send the initial handshake message to start the new// session.cl.sendXml<-&stream{To:cl.Jid.Domain,Version:XMPPVersion}}// Send a request to bind a resource. RFC 3920, section 7.func(cl*Client)bind(){res:=cl.Jid.ResourcebindReq:=&bindIq{}ifres!=""{bindReq.Resource=&res}msg:=&Iq{Header:Header{Type:"set",Id:NextId(),Nested:[]interface{}{bindReq}}}f:=func(stStanza){iq,ok:=st.(*Iq)if!ok{Warn.Log("non-iq response")}ifiq.Type=="error"{Warn.Log("Resource binding failed")}varbindRepl*bindIqfor_,ele:=rangeiq.Nested{ifb,ok:=ele.(*bindIq);ok{bindRepl=bbreak}}ifbindRepl==nil{Warn.Logf("Bad bind reply: %#v",iq)}jidStr:=bindRepl.JidifjidStr==nil||*jidStr==""{Warn.Log("Can't bind empty resource")}jid:=new(JID)iferr:=jid.Set(*jidStr);err!=nil{Warn.Logf("Can't parse JID %s: %s",*jidStr,err)}cl.Jid=*jidInfo.Logf("Bound resource: %s",cl.Jid.String())cl.setStatus(StatusBound)}cl.SetCallback(msg.Id,f)cl.sendXml<-msg}// Register a callback to handle the next XMPP stanza (iq, message, or// presence) with a given id. The provided function will not be called// more than once. If it returns false, the stanza will not be made// available on the normal Client.Recv channel. The callback must not// read from that channel, as deliveries on it cannot proceed until// the handler returns true or false.func(cl*Client)SetCallback(idstring,ffunc(Stanza)){h:=&callback{id:id,f:f}cl.handlers<-h}