diff options
author | xengineering <me@xengineering.eu> | 2023-05-15 21:09:26 +0200 |
---|---|---|
committer | xengineering <me@xengineering.eu> | 2023-05-15 21:09:26 +0200 |
commit | 8023777ac1b0171783e1c55f67a6179d63708272 (patch) | |
tree | 38b43dff5cb3e846f018f9b77c0c76734bb0f832 /xmpp | |
parent | 1769afe3009b5b3cfa3beb3dcf051e41487be113 (diff) | |
download | limox-8023777ac1b0171783e1c55f67a6179d63708272.tar limox-8023777ac1b0171783e1c55f67a6179d63708272.tar.zst limox-8023777ac1b0171783e1c55f67a6179d63708272.zip |
Introduce package xengineering.eu/limox/xmpp
The XMPP logic is now big enough to create a corresponding package for
it.
Diffstat (limited to 'xmpp')
-rw-r--r-- | xmpp/xmpp.go | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/xmpp/xmpp.go b/xmpp/xmpp.go new file mode 100644 index 0000000..64ca494 --- /dev/null +++ b/xmpp/xmpp.go @@ -0,0 +1,188 @@ +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) +} |