// This package implements a simple XMPP client according to RFCs 3920// and 3921, plus the various XEPs at http://xmpp.org/protocols/. The// implementation is structured as a stack of layers, with TCP at the// bottom and the application at the top. The application receives and// sends structures representing XMPP stanzas. Additional stanza// parsers can be inserted into the stack of layers as extensions.packagexmppimport("bytes""crypto/tls""encoding/xml""errors""fmt""io""net""reflect")const(// Version of RFC 3920 that we implement.XMPPVersion="1.0"// Various XML namespaces.NsClient="jabber:client"NsStreams="urn:ietf:params:xml:ns:xmpp-streams"NsStream="http://etherx.jabber.org/streams"NsTLS="urn:ietf:params:xml:ns:xmpp-tls"NsSASL="urn:ietf:params:xml:ns:xmpp-sasl"NsBind="urn:ietf:params:xml:ns:xmpp-bind"NsSession="urn:ietf:params:xml:ns:xmpp-session"NsRoster="jabber:iq:roster"// DNS SRV namesserverSrv="xmpp-server"clientSrv="xmpp-client")// Flow control for preventing sending stanzas until negotiation has// completed.typesendCmdintconst(sendAllowConst=iotasendDenyConstsendAbortConst)var(sendAllowsendCmd=sendAllowConstsendDenysendCmd=sendDenyConstsendAbortsendCmd=sendAbortConst)// A filter can modify the XMPP traffic to or from the remote// server. It's part of an Extension. The filter function will be// called in a new goroutine, so it doesn't need to return. The filter// should close its output when its input is closed.typeFilterfunc(in<-chanStanza,outchan<-Stanza)// Extensions can add stanza filters and/or new XML element types.typeExtensionstruct{// Maps from an XML namespace to a function which constructs a// structure to hold the contents of stanzas in that// namespace.StanzaHandlersmap[xml.Name]reflect.Type// If non-nil, will be called once to start the filter// running. RecvFilter intercepts incoming messages on their// way from the remote server to the application; SendFilter// intercepts messages going the other direction.RecvFilterFilterSendFilterFilter}// The client in a client-server XMPP connection.typeClientstruct{// This client's JID. This will be updated asynchronously by// the time StartSession() returns.JidJIDpasswordstringsaslExpectedstringauthDoneboolhandlerschan*callbackinputControlchansendCmd// Incoming XMPP stanzas from the remote will be published on// this channel. Information which is used by this library to// set up the XMPP stream will not appear here.Recv<-chanStanza// Outgoing XMPP stanzas to the server should be sent to this// channel.Sendchan<-StanzasendXmlchan<-interface{}// The client's roster is also known as the buddy list. It's// the set of contacts which are known to this JID, or which// this JID is known to.RosterRoster// Features advertised by the remote. This will be updated// asynchronously as new features are received throughout the// connection process. It should not be updated once// StartSession() returns.Features*FeaturessendFilterAdd,recvFilterAddchanFilter// Allows the user to override the TLS configuration.tlsConfigtls.Configlayer1*layer1}// Connect to the appropriate server and authenticate as the given JID// with the given password. This function will return as soon as a TCP// connection has been established, but before XMPP stream negotiation// has completed. The negotiation will occur asynchronously, and any// send operation to Client.Send will block until negotiation// (resource binding) is complete. The caller must immediately start// reading from Client.Recv.funcNewClient(jid*JID,passwordstring,tlsconftls.Config,exts[]Extension)(*Client,error){// Include the mandatory extensions.roster:=newRosterExt()exts=append(exts,roster.Extension)exts=append(exts,bindExt)// Resolve the domain in the JID._,srvs,err:=net.LookupSRV(clientSrv,"tcp",jid.Domain)iferr!=nil{returnnil,fmt.Errorf("LookupSrv %s: %v",jid.Domain,err)}iflen(srvs)==0{returnnil,fmt.Errorf("LookupSrv %s: no results",jid.Domain)}vartcp*net.TCPConnfor_,srv:=rangesrvs{addrStr:=fmt.Sprintf("%s:%d",srv.Target,srv.Port)varaddr*net.TCPAddraddr,err=net.ResolveTCPAddr("tcp",addrStr)iferr!=nil{err=fmt.Errorf("ResolveTCPAddr(%s): %s",addrStr,err.Error())continue}tcp,err=net.DialTCP("tcp",nil,addr)iftcp!=nil{break}}iftcp==nil{returnnil,err}cl:=new(Client)cl.Roster=*rostercl.password=passwordcl.Jid=*jidcl.handlers=make(chan*callback,100)cl.inputControl=make(chansendCmd)cl.tlsConfig=tlsconfcl.sendFilterAdd=make(chanFilter)cl.recvFilterAdd=make(chanFilter)extStanza:=make(map[xml.Name]reflect.Type)for_,ext:=rangeexts{fork,v:=rangeext.StanzaHandlers{if_,ok:=extStanza[k];ok{returnnil,fmt.Errorf("duplicate handler %s",k)}extStanza[k]=v}}// Start the transport handler, initially unencrypted.recvReader,recvWriter:=io.Pipe()sendReader,sendWriter:=io.Pipe()cl.layer1=startLayer1(tcp,recvWriter,sendReader)// Start the reader and writer that convert to and from XML.recvXmlCh:=make(chaninterface{})gorecvXml(recvReader,recvXmlCh,extStanza)sendXmlCh:=make(chaninterface{})cl.sendXml=sendXmlChgosendXml(sendWriter,sendXmlCh)// Start the reader and writer that convert between XML and// XMPP stanzas.recvRawXmpp:=make(chanStanza)gocl.recvStream(recvXmlCh,recvRawXmpp)sendRawXmpp:=make(chanStanza)gosendStream(sendXmlCh,sendRawXmpp,cl.inputControl)// Start the manager for the filters that can modify what the// app sees.recvFiltXmpp:=make(chanStanza)cl.Recv=recvFiltXmppgofilterMgr(cl.recvFilterAdd,recvRawXmpp,recvFiltXmpp)sendFiltXmpp:=make(chanStanza)cl.Send=sendFiltXmppgofilterMgr(cl.sendFilterAdd,sendFiltXmpp,sendRawXmpp)// Set up the initial filters.for_,ext:=rangeexts{cl.AddRecvFilter(ext.RecvFilter)cl.AddSendFilter(ext.SendFilter)}// Initial handshake.hsOut:=&stream{To:jid.Domain,Version:XMPPVersion}cl.sendXml<-hsOutreturncl,nil}functee(rio.Reader,wio.Writer,prefixstring){deferfunc(wio.Writer){ifc,ok:=w.(io.Closer);ok{c.Close()}}(w)buf:=bytes.NewBuffer([]uint8(prefix))for{varc[1]byten,_:=r.Read(c[:])ifn==0{break}n,_=w.Write(c[:n])ifn==0{break}buf.Write(c[:n])ifc[0]=='\n'||c[0]=='>'{Debug.Log(buf)buf=bytes.NewBuffer([]uint8(prefix))}}leftover:=buf.String()ifleftover!=""{Debug.Log(buf)}}// bindDone is called when we've finished resource binding (and all// the negotiations that precede it). Now we can start accepting// traffic from the app.func(cl*Client)bindDone(){cl.inputControl<-sendAllow}// Start an XMPP session. A typical XMPP client should call this// immediately after creating the Client in order to start the session// and broadcast an initial presence. The presence can be as simple as// a newly-initialized Presence struct. See RFC 3921, Section// 3. After calling this, a normal client should call Roster.Update().func(cl*Client)StartSession(pr*Presence)error{id:=NextId()iq:=&Iq{Header:Header{To:cl.Jid.Domain,Id:id,Type:"set",Nested:[]interface{}{Generic{XMLName:xml.Name{Space:NsSession,Local:"session"}}}}}ch:=make(chanerror)f:=func(stStanza)bool{iq,ok:=st.(*Iq)if!ok{Warn.Log("iq reply not iq; can't start session")ch<-errors.New("bad session start reply")returnfalse}ifiq.Type=="error"{Warn.Logf("Can't start session: %v",iq)ch<-iq.Errorreturnfalse}ch<-nilreturnfalse}cl.SetCallback(id,f)cl.Send<-iq// Now wait until the callback is called.iferr:=<-ch;err!=nil{returnerr}ifpr!=nil{cl.Send<-pr}returnnil}