package main import ( "crypto/tls" "crypto/x509" "encoding/xml" "log" "os" ) type XmppEvent uint8 const ( XmppDisconnect XmppEvent = iota XmppConnect ) func xmpp(rxChan chan GuiEvent, txChan chan any, jid string, pwd string) { defer func() { txChan <- XmppDisconnect }() conn, err := setupConn(jid) if err != nil { log.Print(err) return } defer conn.Close() receiver := make(chan xml.Token) termination := make(chan bool) go rxRoutine(conn, receiver, termination) defer func() { termination <- true }() enc := xml.NewEncoder(conn) defer enc.Close() dbg := xml.NewEncoder(os.Stdout) defer dbg.Close() end := sendStreamStart(enc, dbg, jid) defer sendStreamEnd(enc, dbg, end) txChan <- XmppConnect for { select { case ev := <-rxChan: switch ev { case Disconnect: return default: log.Printf("Unknown GuiEvent '%d'!\n", ev) } case rx := <-receiver: dbg.Indent("S: ", " ") dbg.EncodeToken(rx) dbg.Flush() } } } func setupConn(jid string) (*tls.Conn, error) { domain := domainpart(jid) roots, err := x509.SystemCertPool() if err != nil { return nil, err } return tls.Dial("tcp", domain+":"+"5223", &tls.Config{RootCAs: roots}) } func rxRoutine(conn *tls.Conn, tokens chan xml.Token, terminator chan bool) { dec := xml.NewDecoder(conn) for { select { case <-terminator: return default: t, err := dec.Token() if err != nil { log.Print(err) } if t != nil { tokens <- t } } } } func sendStreamStart(enc *xml.Encoder, dbg *xml.Encoder, jid string) xml.EndElement { start := xml.StartElement{ xml.Name{"jabber:client", "stream:stream"}, []xml.Attr{ xml.Attr{xml.Name{"", "from"}, jid}, xml.Attr{xml.Name{"", "to"}, domainpart(jid)}, xml.Attr{xml.Name{"", "version"}, "1.0"}, xml.Attr{xml.Name{"", "xml:lang"}, "en"}, xml.Attr{xml.Name{"", "xmlns:stream"}, "http://etherx.jabber.org/streams"}, }, } err := enc.EncodeToken(start) if err != nil { log.Println("Could not encode stream start to TCP channel!") } err = enc.Flush() if err != nil { log.Println("Could not flush TCP channel XML encoder!") } dbg.Indent("C: ", " ") err = dbg.EncodeToken(start) if err != nil { log.Println("Could not encode stream start to debug output!") } err = dbg.Flush() if err != nil { log.Println("Could not flush debug XML encoder!") } return start.End() } func sendStreamEnd(enc *xml.Encoder, dbg *xml.Encoder, end xml.EndElement) { err := enc.EncodeToken(end) if err != nil { log.Println("Could not encode stream end to TCP channel!") } err = enc.Flush() if err != nil { log.Println("Could not flush TCP channel XML encoder!") } dbg.Indent("C: ", " ") err = dbg.EncodeToken(end) if err != nil { log.Println("Could not encode stream end to debug output!") } err = dbg.Flush() if err != nil { log.Println("Could not flush debug XML encoder!") } } // domainpart extracts the domain name from a JID / XMPP address. See // https://datatracker.ietf.org/doc/html/rfc7622#section-3.2 for details. func domainpart(jid string) string { list := []rune(jid) for i, v := range list { if v == '/' { list = list[:i] break } } for i, v := range list { if v == '@' { list = list[i+1:] break } } return string(list) }