// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "context" "fmt" "io" "log" "runtime" "time" "github.com/beevik/etree" // BSD-2-clause "github.com/xmppo/go-xmpp" // BSD-3-Clause ) func sendIQ(client *xmpp.Client, iqc chan xmpp.IQ, target string, iQtype string, content string) (xmpp.IQ, error) { var iq xmpp.IQ id := getID() c := make(chan xmpp.IQ, defaultBufferSize) go getIQ(id, c, iqc) _, err := client.RawInformation(client.JID(), target, id, iQtype, content) if err != nil { return iq, fmt.Errorf("sendIQ: failed to send iq: %w", err) } select { case iq = <-c: case <-time.After(60 * time.Second): return iq, fmt.Errorf("sendIQ: server didn't reply to IQ: %s", content) } return iq, nil } func getIQ(id string, c chan xmpp.IQ, iqc chan xmpp.IQ) { for { iq := <-iqc if iq.ID == id { c <- iq return } } } func rcvStanzas(client *xmpp.Client, ctx context.Context, iqc chan xmpp.IQ, msgc chan xmpp.Chat) { var received interface{} r := make(chan interface{}, defaultBufferSize) e := make(chan error, defaultBufferSize) go func() { for { select { case <-ctx.Done(): return default: } rcv, err := client.Recv() if err != nil { e <- err } else { r <- rcv } } }() for { select { case <-ctx.Done(): return case err := <-e: if err != nil { if err != io.EOF { closeAndExit(client, err) return } return } case received = <-r: } switch v := received.(type) { case xmpp.Chat: msgc <- v case xmpp.IQ: switch v.Type { case "get": var xmlns *etree.Attr iq := etree.NewDocument() err := iq.ReadFromBytes(v.Query) if err != nil { log.Println("Couldn't parse IQ:", string(v.Query), err) } query := iq.SelectElement("query") if query != nil { xmlns = query.SelectAttr("xmlns") } if xmlns == nil { break } switch xmlns.Value { case nsDiscoInfo: root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", "result") reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) replyQuery := reply.CreateElement("query") replyQuery.CreateAttr("xmlns", nsDiscoInfo) identity := replyQuery.CreateElement("identity") identity.CreateAttr("category", "client") identity.CreateAttr("type", "bot") identity.CreateAttr("name", "go-sendxmpp") feat := replyQuery.CreateElement("feature") feat.CreateAttr("var", nsDiscoInfo) feat2 := replyQuery.CreateElement("feature") feat2.CreateAttr("var", nsVersion) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } case nsVersion: root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", "result") reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) replyQuery := reply.CreateElement("query") replyQuery.CreateAttr("xmlns", nsVersion) rqName := replyQuery.CreateElement("name") rqName.CreateText("go-sendxmpp") rqVersion := replyQuery.CreateElement("version") rqVersion.CreateText(version) rqOS := replyQuery.CreateElement("os") rqOS.CreateText(runtime.GOOS) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } default: root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", strError) reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) errorReply := reply.CreateElement(strError) errorReply.CreateAttr("type", "cancel") su := errorReply.CreateElement("service-unavailable") su.CreateAttr("xmlns", nsXMPPStanzas) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } } case "set": root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", strError) reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) errorReply := reply.CreateElement(strError) errorReply.CreateAttr("type", "cancel") su := errorReply.CreateElement("service-unavailable") su.CreateAttr("xmlns", nsXMPPStanzas) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } case "reply", strError: iqc <- v default: iqc <- v } } } }