summaryrefslogtreecommitdiff
path: root/xmpp
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2023-05-15 21:09:26 +0200
committerxengineering <me@xengineering.eu>2023-05-15 21:09:26 +0200
commit8023777ac1b0171783e1c55f67a6179d63708272 (patch)
tree38b43dff5cb3e846f018f9b77c0c76734bb0f832 /xmpp
parent1769afe3009b5b3cfa3beb3dcf051e41487be113 (diff)
downloadlimox-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.go188
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)
+}