package xmpp import ( "crypto/tls" "crypto/x509" "encoding/xml" "log" "os" ) type Event uint8 const ( DisconnectEvent Event = iota ConnectEvent ShouldDisconnectEvent ) func Run(rxChan chan Event, txChan chan any, jid string, pwd string) { conn, err := setupConn(jid) if err != nil { log.Print(err) return } defer conn.Close() receiver := newXmppReceiver(conn) go receiver.run() defer receiver.stop() 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 <- ConnectEvent defer func() { txChan <- DisconnectEvent }() for { select { case ev := <-rxChan: switch ev { case ShouldDisconnectEvent: return default: log.Printf("Unknown Event '%d'!\n", ev) } case rx := <-receiver.data: dbg.Indent("S: ", " ") dbg.EncodeToken(rx) dbg.Flush() } } } type xmppReceiver struct { terminator chan bool data chan xml.Token decoder *xml.Decoder } func newXmppReceiver(conn *tls.Conn) xmppReceiver { return xmppReceiver{ make(chan bool), make(chan xml.Token), xml.NewDecoder(conn), } } func (r *xmppReceiver) run() { for { select { case <-r.terminator: return default: t, err := r.decoder.Token() if err != nil { log.Print(err) } if t != nil { c := xml.CopyToken(t) r.data <- c } } } } func (r *xmppReceiver) stop() { r.terminator <- true } 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 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) }