From 3cac662ef99366dea7bec383ba6704b3a5292d93 Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 21:07:07 +0200 Subject: Rename stream_pair.go to streams.go The fact that it is actually about a pair of XML streams is obvious and not that relevant. A shorter name has a higher priority. --- xmpp/streams.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 xmpp/streams.go (limited to 'xmpp/streams.go') diff --git a/xmpp/streams.go b/xmpp/streams.go new file mode 100644 index 0000000..87df86a --- /dev/null +++ b/xmpp/streams.go @@ -0,0 +1,131 @@ +package xmpp + +import ( + "encoding/xml" + "log" +) + +func runStreamPair(s *session) { + end := openStream(s) + defer closeStream(s, end) + + buf := newElementBuffer() + + for { + select { + case data := <-s.in: + switch data.(type) { + case SessionShouldDisconnect: + return + default: + log.Printf("Unknown data '%d'!\n", data) + } + case t := <-s.rx: + err := buf.add(t) + if err != nil { + log.Printf("Could not add XML token to buffer: %v\n", err) + return + } + if buf.isComplete() { + element := buf.reset() + route(s, element, getRoutingTable()) + } + } + } +} + +func openStream(s *session) xml.EndElement { + start := xml.StartElement{ + xml.Name{"jabber:client", "stream:stream"}, + []xml.Attr{ + xml.Attr{xml.Name{"", "from"}, s.jid}, + xml.Attr{xml.Name{"", "to"}, domainpart(s.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"}, + }, + } + end := start.End() + + err := s.ed.encodeToken(start) + if err != nil { + log.Println("Could not encode stream start!") + } + + syncStreams(s) + + return end +} + +// syncStreams drops XML tokens from the receiving stream until an +// xml.StartElement with the local name `stream` is received. If this function +// is called after opening a new stream in the sending direction it is ensured +// that both streams directions work on the same stream level and are in sync. +// Tokens received which are not a stream StartElement are not handled but +// logged since this should not happen. +func syncStreams(s *session) { + for { + select { + case data := <-s.in: + switch data.(type) { + case SessionShouldDisconnect: + return + default: + log.Printf("Unhandled data '%d' during stream sync!\n", data) + } + case t := <-s.rx: + switch token := t.(type) { + case xml.StartElement: + if token.Name.Local == "stream" { + return + } + } + log.Printf("Unhandled XML token '%v' during stream sync!\n", t) + } + } +} + +func closeStream(s *session, end xml.EndElement) { + err := s.ed.encodeToken(end) + if err != nil { + log.Println("Could not encode stream end!") + } +} + +func streamFeaturesHandler(s *session, e []xml.Token) { + if hasSaslPlain(e) { + s.sasl() + return + } + + if hasBind(e) { + s.sendBind() + return + } + + log.Println("Stream has no implemented features!") +} + +func iqHandler(s *session, e []xml.Token) { + isResult := false + idMatches := false + + result := xml.Attr{xml.Name{"", "type"}, "result"} + id := xml.Attr{xml.Name{"", "id"}, s.resourceReq} + + switch start := e[0].(type) { + case xml.StartElement: + for _, v := range start.Attr { + if v == result { + isResult = true + } + if v == id { + idMatches = true + } + } + + if isResult && idMatches { + s.sendPresence() + } + } +} -- cgit v1.2.3-70-g09d2 From 2fade1039c1842f08b30da5c95b5542b57e38ec6 Mon Sep 17 00:00:00 2001 From: xengineering Date: Mon, 3 Jul 2023 22:17:15 +0200 Subject: Move xml.Encoder to session struct The encoderDecoder sub-struct of the session struct should be removed in little steps. This is the first one. --- xmpp/jid.go | 2 +- xmpp/presence.go | 2 +- xmpp/sasl.go | 2 +- xmpp/session.go | 6 ++++++ xmpp/streams.go | 4 ++-- xmpp/xml.go | 12 +++--------- 6 files changed, 14 insertions(+), 14 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/jid.go b/xmpp/jid.go index 332073b..83772fd 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -81,7 +81,7 @@ func (s *session) sendBind() { inner.Bind.Xmlns = "urn:ietf:params:xml:ns:xmpp-bind" inner.Bind.Resource.Content = "limox-" + fmt.Sprintf("%08x", rand.Uint32()) - err := s.ed.tx.EncodeElement(inner, start) + err := s.tx.EncodeElement(inner, start) if err != nil { log.Println("Could not encode ressource binding!") } diff --git a/xmpp/presence.go b/xmpp/presence.go index 4adae6e..e2b1841 100644 --- a/xmpp/presence.go +++ b/xmpp/presence.go @@ -13,7 +13,7 @@ func (s *session) sendPresence() { []xml.Attr{}, } - err := s.ed.tx.EncodeElement(presence{}, start) + err := s.tx.EncodeElement(presence{}, start) if err != nil { log.Println("Could not encode presence!") return diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 24edc9a..0c13f36 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -23,7 +23,7 @@ func (s *session) sasl() { inner.Payload = make([]byte, base64.StdEncoding.EncodedLen(len(data))) base64.StdEncoding.Encode(inner.Payload, data) - err := s.ed.tx.EncodeElement(inner, start) + err := s.tx.EncodeElement(inner, start) if err != nil { log.Println("Could not encode SASL PLAIN element!") } diff --git a/xmpp/session.go b/xmpp/session.go index a43e4f4..b4a8fab 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/xml" + "io" "log" ) @@ -18,6 +19,7 @@ type session struct { out chan<- any transport *tls.Conn ed encoderDecoder + tx *xml.Encoder rx chan xml.Token resourceReq string } @@ -49,6 +51,10 @@ func (s *session) run() { go s.ed.run() defer func() { s.ed.terminator <- true }() + lw := logger{"[TX] "} + w := io.MultiWriter(s.transport, lw) + s.tx = xml.NewEncoder(w) + s.out <- SessionConnect{} runStreamPair(s) diff --git a/xmpp/streams.go b/xmpp/streams.go index 87df86a..5ba4c1d 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -47,7 +47,7 @@ func openStream(s *session) xml.EndElement { } end := start.End() - err := s.ed.encodeToken(start) + err := s.encodeToken(start) if err != nil { log.Println("Could not encode stream start!") } @@ -86,7 +86,7 @@ func syncStreams(s *session) { } func closeStream(s *session, end xml.EndElement) { - err := s.ed.encodeToken(end) + err := s.encodeToken(end) if err != nil { log.Println("Could not encode stream end!") } diff --git a/xmpp/xml.go b/xmpp/xml.go index b0ea77b..f547210 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -9,7 +9,6 @@ import ( type encoderDecoder struct { session *session - tx *xml.Encoder rx *xml.Decoder terminator chan bool } @@ -19,11 +18,6 @@ func newEncoderDecoder(s *session) encoderDecoder { ed.session = s - lw := logger{"[TX] "} - w := io.MultiWriter(s.transport, lw) - ed.tx = xml.NewEncoder(w) - ed.tx.Indent("", "") - lr := logger{"[RX] "} r := io.TeeReader(s.transport, lr) ed.rx = xml.NewDecoder(r) @@ -31,7 +25,7 @@ func newEncoderDecoder(s *session) encoderDecoder { return ed } -func (ed *encoderDecoder) encodeToken(t xml.Token) error { +func (s *session) encodeToken(t xml.Token) error { var err error defer func() { if err != nil { @@ -39,11 +33,11 @@ func (ed *encoderDecoder) encodeToken(t xml.Token) error { } }() - err = ed.tx.EncodeToken(t) + err = s.tx.EncodeToken(t) if err != nil { return err } - err = ed.tx.Flush() + err = s.tx.Flush() if err != nil { return err } -- cgit v1.2.3-70-g09d2 From 9f87ef34c589825d65824a8c9210fed5bf92f94d Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 12:33:12 +0200 Subject: Remove runStreamPair() This will not be used in the new RX concept. --- xmpp/sasl.go | 1 - xmpp/session.go | 2 -- xmpp/streams.go | 29 ----------------------------- 3 files changed, 32 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 0c13f36..a20ae56 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -60,7 +60,6 @@ func hasSaslPlain(e []xml.Token) bool { } func saslSuccessHandler(s *session, e []xml.Token) { - runStreamPair(s) } func saslFailureHandler(s *session, e []xml.Token) { diff --git a/xmpp/session.go b/xmpp/session.go index 6a5e646..f18fd2a 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -57,8 +57,6 @@ func (s *session) run() { s.tx = xml.NewEncoder(w) s.out <- SessionConnect{} - - runStreamPair(s) } func (s *session) startTransport() error { diff --git a/xmpp/streams.go b/xmpp/streams.go index 5ba4c1d..388be4d 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -5,35 +5,6 @@ import ( "log" ) -func runStreamPair(s *session) { - end := openStream(s) - defer closeStream(s, end) - - buf := newElementBuffer() - - for { - select { - case data := <-s.in: - switch data.(type) { - case SessionShouldDisconnect: - return - default: - log.Printf("Unknown data '%d'!\n", data) - } - case t := <-s.rx: - err := buf.add(t) - if err != nil { - log.Printf("Could not add XML token to buffer: %v\n", err) - return - } - if buf.isComplete() { - element := buf.reset() - route(s, element, getRoutingTable()) - } - } - } -} - func openStream(s *session) xml.EndElement { start := xml.StartElement{ xml.Name{"jabber:client", "stream:stream"}, -- cgit v1.2.3-70-g09d2 From ea98ee187477051444bbf548757af6336d333862 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 12:58:13 +0200 Subject: Re-implement stream open and close This is more suitable for the new RX concept. --- xmpp/session.go | 21 ++++++++++++++++++--- xmpp/streams.go | 53 +++++++++++++++-------------------------------------- xmpp/xml.go | 19 ++++++++++++------- 3 files changed, 45 insertions(+), 48 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/session.go b/xmpp/session.go index f18fd2a..4be3386 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -39,8 +39,6 @@ func StartSession(out chan<- any, jid string, pwd string) (in chan<- any) { } func (s *session) run() { - defer func() { s.out <- SessionDisconnect{} }() - err := s.startTransport() if err != nil { return @@ -55,8 +53,25 @@ func (s *session) run() { lw := logger{"[TX] "} w := io.MultiWriter(s.transport, lw) s.tx = xml.NewEncoder(w) + defer s.tx.Close() + + openStream(s.tx, s.jid) + defer closeStream(s.tx) - s.out <- SessionConnect{} + s.out <- SessionConnect{} // TODO this should be sent after initial presence + defer func() { s.out <- SessionDisconnect{} }() + + for { + select { + case e := <-s.rx: + log.Print(e) + case signal := <-s.in: + switch signal.(type) { + case SessionShouldDisconnect: + return + } + } + } } func (s *session) startTransport() error { diff --git a/xmpp/streams.go b/xmpp/streams.go index 388be4d..b9c0cb7 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -5,62 +5,39 @@ import ( "log" ) -func openStream(s *session) xml.EndElement { +func openStream(e *xml.Encoder, jid string) { start := xml.StartElement{ xml.Name{"jabber:client", "stream:stream"}, []xml.Attr{ - xml.Attr{xml.Name{"", "from"}, s.jid}, - xml.Attr{xml.Name{"", "to"}, domainpart(s.jid)}, + 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"}, }, } - end := start.End() - err := s.encodeToken(start) + err := e.EncodeToken(start) if err != nil { log.Println("Could not encode stream start!") } - - syncStreams(s) - - return end -} - -// syncStreams drops XML tokens from the receiving stream until an -// xml.StartElement with the local name `stream` is received. If this function -// is called after opening a new stream in the sending direction it is ensured -// that both streams directions work on the same stream level and are in sync. -// Tokens received which are not a stream StartElement are not handled but -// logged since this should not happen. -func syncStreams(s *session) { - for { - select { - case data := <-s.in: - switch data.(type) { - case SessionShouldDisconnect: - return - default: - log.Printf("Unhandled data '%d' during stream sync!\n", data) - } - case t := <-s.rx: - switch token := t.(type) { - case xml.StartElement: - if token.Name.Local == "stream" { - return - } - } - log.Printf("Unhandled XML token '%v' during stream sync!\n", t) - } + err = e.Flush() + if err != nil { + log.Println("Could not flush after stream start!") } } -func closeStream(s *session, end xml.EndElement) { - err := s.encodeToken(end) +func closeStream(e *xml.Encoder) { + end := xml.EndElement{xml.Name{"jabber:client", "stream:stream"}} + + err := e.EncodeToken(end) if err != nil { log.Println("Could not encode stream end!") } + err = e.Flush() + if err != nil { + log.Println("Could not flush after stream end!") + } } func streamFeaturesHandler(s *session, e []xml.Token) { diff --git a/xmpp/xml.go b/xmpp/xml.go index 14c6637..36d7eb1 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -22,13 +22,18 @@ func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { default: t, err := d.Token() if t != nil && err == nil { - switch t.(type) { - case xml.ProcInst: - case xml.Directive: - case xml.Comment: - default: - c := xml.CopyToken(t) - chn <- c + switch e := t.(type) { + case xml.StartElement: + if e.Name.Local == "stream" { + // new server-side stream TODO what to do with this info? + } else { +// route(&e, &d, chn, getRoutingTable()) + } + case xml.EndElement: + if e.Name.Local == "stream" { + // TODO end complete session + return + } } } if err != nil { -- cgit v1.2.3-70-g09d2 From 92534f5af88b42665ad44f2495fe5dfb116d3406 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 13:48:34 +0200 Subject: First working version of new RX concept This uses xml.Decoder.DecodeElement() which makes parsing way easier. This first step is just able to parse stream features partially. --- xmpp/router.go | 38 ++++++++------------------------------ xmpp/session.go | 4 ++-- xmpp/streams.go | 33 +++++++++++++++++++-------------- xmpp/xml.go | 4 ++-- 4 files changed, 31 insertions(+), 48 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/router.go b/xmpp/router.go index 1e21c9b..d437b28 100644 --- a/xmpp/router.go +++ b/xmpp/router.go @@ -2,7 +2,6 @@ package xmpp import ( "encoding/xml" - "log" ) // routingTable is a data structure which contains routing information for XML @@ -12,7 +11,7 @@ import ( // entry of the routingTable. type routingTable []struct { name xml.Name - handler func(*session, []xml.Token) + handler func(s *xml.StartElement, d *xml.Decoder, c chan<- any) } // getRoutingTable returns the routing table used in @@ -23,40 +22,19 @@ type routingTable []struct { func getRoutingTable() routingTable { return routingTable{ {xml.Name{`http://etherx.jabber.org/streams`, `features`}, streamFeaturesHandler}, - {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}, saslSuccessHandler}, - {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `failure`}, saslFailureHandler}, - {xml.Name{`jabber:client`, `iq`}, iqHandler}, +// {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}, saslSuccessHandler}, +// {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `failure`}, saslFailureHandler}, +// {xml.Name{`jabber:client`, `iq`}, iqHandler}, } } // route determines the correct handler function for the given XML element by a // given routingTable. In addition it executes the determined handler function. // If no handler function is found an error message is send via the log module. -func route(s *session, e []xml.Token, t routingTable) { - var name xml.Name - - // TODO a stronger definition of an XML element (as here - // https://www.w3schools.com/xml/xml_elements.asp) would define that the - // first Token of an element is a StartElement token. This would make this - // code easier. - escape := false - for _, token := range e { - switch s := token.(type) { - case xml.StartElement: - name = s.Name - escape = true - } - if escape { - break - } - } - - for _, r := range t { - if name == r.name { - r.handler(s, e) - return +func route(s *xml.StartElement, d *xml.Decoder, c chan<- any, t routingTable) { + for _, v := range t { + if v.name == (*s).Name { + v.handler(s, d, c) } } - - log.Println("Could not route XML element") } diff --git a/xmpp/session.go b/xmpp/session.go index 4be3386..6abc343 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -20,7 +20,7 @@ type session struct { out chan<- any transport *tls.Conn tx *xml.Encoder - rx chan xml.Token + rx chan any resourceReq string } @@ -31,7 +31,7 @@ func StartSession(out chan<- any, jid string, pwd string) (in chan<- any) { s.pwd = pwd s.in = make(chan any) s.out = out - s.rx = make(chan xml.Token, 0) + s.rx = make(chan any, 0) go s.run() diff --git a/xmpp/streams.go b/xmpp/streams.go index b9c0cb7..9c90554 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -40,20 +40,6 @@ func closeStream(e *xml.Encoder) { } } -func streamFeaturesHandler(s *session, e []xml.Token) { - if hasSaslPlain(e) { - s.sasl() - return - } - - if hasBind(e) { - s.sendBind() - return - } - - log.Println("Stream has no implemented features!") -} - func iqHandler(s *session, e []xml.Token) { isResult := false idMatches := false @@ -77,3 +63,22 @@ func iqHandler(s *session, e []xml.Token) { } } } + +type streamFeatures struct { + Mechanisms struct { + Items []struct { + Type string `xml:",innerxml"` + } `xml:"mechanism"` + } `xml:"mechanisms"` +} + +func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { + e := streamFeatures{} + + err := d.DecodeElement(&e, s) + if err != nil { + log.Printf("Could not decode stream features: %v\n", err) + } + + c <- e +} diff --git a/xmpp/xml.go b/xmpp/xml.go index 36d7eb1..470a2ef 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -9,7 +9,7 @@ import ( "log" ) -func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { +func runRx(ctx context.Context, chn chan<- any, conn *tls.Conn) { l := logger{"[RX] "} r := io.TeeReader(conn, l) @@ -27,7 +27,7 @@ func runRx(ctx context.Context, chn chan xml.Token, conn *tls.Conn) { if e.Name.Local == "stream" { // new server-side stream TODO what to do with this info? } else { -// route(&e, &d, chn, getRoutingTable()) + route(&e, d, chn, getRoutingTable()) } case xml.EndElement: if e.Name.Local == "stream" { -- cgit v1.2.3-70-g09d2 From e5257c8a9c3dfcda52fb96fcac2ac81aefb52e55 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 13:57:26 +0200 Subject: Reduce complexity of streamFeatures struct --- xmpp/streams.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/streams.go b/xmpp/streams.go index 9c90554..cc83934 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -65,11 +65,9 @@ func iqHandler(s *session, e []xml.Token) { } type streamFeatures struct { - Mechanisms struct { - Items []struct { - Type string `xml:",innerxml"` - } `xml:"mechanism"` - } `xml:"mechanisms"` + Mechanisms []struct { + Type string `xml:",chardata"` + } `xml:"mechanisms>mechanism"` } func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { -- cgit v1.2.3-70-g09d2 From 3826f3ee7850590142fe51747447dffece332762 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 14:01:51 +0200 Subject: Just pass parsed data if successful --- xmpp/streams.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/streams.go b/xmpp/streams.go index cc83934..b55b6f0 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -76,7 +76,7 @@ func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { err := d.DecodeElement(&e, s) if err != nil { log.Printf("Could not decode stream features: %v\n", err) + } else { + c <- e } - - c <- e } -- cgit v1.2.3-70-g09d2 From 1d3dfa5b93000bc4109ba49ea018e72fbf4f5753 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 14:06:36 +0200 Subject: Further reduce complexity of streamFeatures struct --- xmpp/streams.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/streams.go b/xmpp/streams.go index b55b6f0..b7cde3e 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -65,9 +65,7 @@ func iqHandler(s *session, e []xml.Token) { } type streamFeatures struct { - Mechanisms []struct { - Type string `xml:",chardata"` - } `xml:"mechanisms>mechanism"` + SaslMechanisms []string `xml:"mechanisms>mechanism"` } func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { -- cgit v1.2.3-70-g09d2 From 3a5e9fce02264eeed884b15781593479fda9296a Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 14:25:21 +0200 Subject: Rework routing completely --- xmpp/router.go | 40 ---------------------------------------- xmpp/routing.go | 21 +++++++++++++++++++++ xmpp/streams.go | 11 ----------- xmpp/xml.go | 2 +- 4 files changed, 22 insertions(+), 52 deletions(-) delete mode 100644 xmpp/router.go create mode 100644 xmpp/routing.go (limited to 'xmpp/streams.go') diff --git a/xmpp/router.go b/xmpp/router.go deleted file mode 100644 index d437b28..0000000 --- a/xmpp/router.go +++ /dev/null @@ -1,40 +0,0 @@ -package xmpp - -import ( - "encoding/xml" -) - -// routingTable is a data structure which contains routing information for XML -// elements. The xml.StartElement at the beginning of an XML element has a name -// containing the XML namespace and a local name. Based on this compisition -// which forms the xml.Name the appropriate handler function is defined by each -// entry of the routingTable. -type routingTable []struct { - name xml.Name - handler func(s *xml.StartElement, d *xml.Decoder, c chan<- any) -} - -// getRoutingTable returns the routing table used in -// xengineering.eu/limox/xmpp. Since Go does not allow such a datatype as a -// constant such a function is a simple yet inefficient approach to guarantee -// that an unmodified routing table is delivered to each user. A global -// variable would have the problem that it could be altered during execution. -func getRoutingTable() routingTable { - return routingTable{ - {xml.Name{`http://etherx.jabber.org/streams`, `features`}, streamFeaturesHandler}, -// {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}, saslSuccessHandler}, -// {xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `failure`}, saslFailureHandler}, -// {xml.Name{`jabber:client`, `iq`}, iqHandler}, - } -} - -// route determines the correct handler function for the given XML element by a -// given routingTable. In addition it executes the determined handler function. -// If no handler function is found an error message is send via the log module. -func route(s *xml.StartElement, d *xml.Decoder, c chan<- any, t routingTable) { - for _, v := range t { - if v.name == (*s).Name { - v.handler(s, d, c) - } - } -} diff --git a/xmpp/routing.go b/xmpp/routing.go new file mode 100644 index 0000000..e8aa4ed --- /dev/null +++ b/xmpp/routing.go @@ -0,0 +1,21 @@ +package xmpp + +import ( + "encoding/xml" + "log" +) + +func route(s *xml.StartElement, d *xml.Decoder, c chan<- any) { + switch (*s).Name { + case xml.Name{`http://etherx.jabber.org/streams`, `features`}: + data := streamFeatures{} + err := d.DecodeElement(&data, s) + if err != nil { + log.Printf("Could not decode stream features: %v\n", err) + } else { + log.Print(data) // TODO + } + default: + d.Skip() + } +} diff --git a/xmpp/streams.go b/xmpp/streams.go index b7cde3e..3aca8a2 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -67,14 +67,3 @@ func iqHandler(s *session, e []xml.Token) { type streamFeatures struct { SaslMechanisms []string `xml:"mechanisms>mechanism"` } - -func streamFeaturesHandler(s *xml.StartElement, d *xml.Decoder, c chan<- any) { - e := streamFeatures{} - - err := d.DecodeElement(&e, s) - if err != nil { - log.Printf("Could not decode stream features: %v\n", err) - } else { - c <- e - } -} diff --git a/xmpp/xml.go b/xmpp/xml.go index 470a2ef..e6fccee 100644 --- a/xmpp/xml.go +++ b/xmpp/xml.go @@ -27,7 +27,7 @@ func runRx(ctx context.Context, chn chan<- any, conn *tls.Conn) { if e.Name.Local == "stream" { // new server-side stream TODO what to do with this info? } else { - route(&e, d, chn, getRoutingTable()) + route(&e, d, chn) } case xml.EndElement: if e.Name.Local == "stream" { -- cgit v1.2.3-70-g09d2 From 2c71877e392da6c2691827160142e95142f7bea6 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 21:24:04 +0200 Subject: Re-implement SASL Was broken because of switch to new RX concept. --- xmpp/routing.go | 4 ++-- xmpp/session.go | 2 +- xmpp/streams.go | 21 +++++++++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/routing.go b/xmpp/routing.go index b184b1c..a9dd8b6 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -23,10 +23,10 @@ func parse[T any](data T, s *xml.StartElement, d *xml.Decoder, c chan<- any) { } } -func handle(element any) { +func handle(s *session, element any) { switch t := element.(type) { case streamFeatures: - log.Println("Handling stream features ...") + handleStreamFeatures(s, t) default: log.Printf("Unknown parsed element: %v", t) } diff --git a/xmpp/session.go b/xmpp/session.go index a4120e9..7a07280 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -64,7 +64,7 @@ func (s *session) run() { for { select { case e := <-s.rx: - handle(e) + handle(s, e) case signal := <-s.in: switch signal.(type) { case SessionShouldDisconnect: diff --git a/xmpp/streams.go b/xmpp/streams.go index 3aca8a2..8f6fd03 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -5,6 +5,23 @@ import ( "log" ) +type streamFeatures struct { + SaslMechanisms []string `xml:"mechanisms>mechanism"` +} + +func handleStreamFeatures(s *session, f streamFeatures) { + if len(f.SaslMechanisms) > 0 { + for _, v := range f.SaslMechanisms { + if v == "PLAIN" { + s.sasl() + return + } + } + log.Println("No compatible SASL mechanism found!") + return + } +} + func openStream(e *xml.Encoder, jid string) { start := xml.StartElement{ xml.Name{"jabber:client", "stream:stream"}, @@ -63,7 +80,3 @@ func iqHandler(s *session, e []xml.Token) { } } } - -type streamFeatures struct { - SaslMechanisms []string `xml:"mechanisms>mechanism"` -} -- cgit v1.2.3-70-g09d2 From ed6b4e818f4090c0c707fab49093bc4c3cc3ac20 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 21:48:23 +0200 Subject: Add namespace to streamFeatures struct This prevents collisions. --- xmpp/streams.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/streams.go b/xmpp/streams.go index 8f6fd03..b9cd4cd 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -6,7 +6,7 @@ import ( ) type streamFeatures struct { - SaslMechanisms []string `xml:"mechanisms>mechanism"` + SaslMechanisms []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms>mechanism"` } func handleStreamFeatures(s *session, f streamFeatures) { -- cgit v1.2.3-70-g09d2 From a23ba089a4e715e68b8c8d4179290692215784a2 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 22:04:05 +0200 Subject: Re-implement resource binding and presence This was removed for refactoring. --- xmpp/jid.go | 27 ++++++++++++--------------- xmpp/routing.go | 8 ++++++++ xmpp/sasl.go | 6 ++++++ xmpp/streams.go | 8 ++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/jid.go b/xmpp/jid.go index 83772fd..c732027 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -41,21 +41,6 @@ func username(jid string) string { return "" } -func hasBind(e []xml.Token) bool { - bind := xml.Name{`urn:ietf:params:xml:ns:xmpp-bind`, `bind`} - - for _, v := range e { - switch s := v.(type) { - case xml.StartElement: - if s.Name == bind { - return true - } - } - } - - return false -} - type bindRequest struct { Bind struct { Xmlns string `xml:"xmlns,attr"` @@ -86,3 +71,15 @@ func (s *session) sendBind() { log.Println("Could not encode ressource binding!") } } + +type iqResponse struct { + Jid string `xml:"urn:ietf:params:xml:ns:xmpp-bind bind>jid"` +} + +func handleIqResponse(s *session, i iqResponse) { + if i.Jid != "" { + s.jid = i.Jid + s.sendPresence() + return + } +} diff --git a/xmpp/routing.go b/xmpp/routing.go index a9dd8b6..5cd2040 100644 --- a/xmpp/routing.go +++ b/xmpp/routing.go @@ -9,6 +9,10 @@ func route(s *xml.StartElement, d *xml.Decoder, c chan<- any) { switch (*s).Name { case xml.Name{`http://etherx.jabber.org/streams`, `features`}: parse(streamFeatures{}, s, d, c) + case xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `success`}: + parse(saslSuccess{}, s, d, c) + case xml.Name{`jabber:client`, `iq`}: + parse(iqResponse{}, s, d, c) default: d.Skip() } @@ -27,6 +31,10 @@ func handle(s *session, element any) { switch t := element.(type) { case streamFeatures: handleStreamFeatures(s, t) + case saslSuccess: + handleSaslSuccess(s) + case iqResponse: + handleIqResponse(s, t) default: log.Printf("Unknown parsed element: %v", t) } diff --git a/xmpp/sasl.go b/xmpp/sasl.go index a20ae56..5fac934 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -10,6 +10,12 @@ type saslRequest struct { Payload []byte `xml:",chardata"` } +type saslSuccess struct {} + +func handleSaslSuccess(s *session) { + openStream(s.tx, s.jid) +} + func (s *session) sasl() { start := xml.StartElement{ xml.Name{"urn:ietf:params:xml:ns:xmpp-sasl", "auth"}, diff --git a/xmpp/streams.go b/xmpp/streams.go index b9cd4cd..18a5e6a 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -7,9 +7,12 @@ import ( type streamFeatures struct { SaslMechanisms []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms>mechanism"` + Bind *bool `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` } func handleStreamFeatures(s *session, f streamFeatures) { + log.Print(f) + if len(f.SaslMechanisms) > 0 { for _, v := range f.SaslMechanisms { if v == "PLAIN" { @@ -20,6 +23,11 @@ func handleStreamFeatures(s *session, f streamFeatures) { log.Println("No compatible SASL mechanism found!") return } + + if f.Bind != nil { + s.sendBind() + return + } } func openStream(e *xml.Encoder, jid string) { -- cgit v1.2.3-70-g09d2 From 4c9c6c91f9dfca9fd17731d5b0e94aaaace4a137 Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 22:09:13 +0200 Subject: Remove unused code --- xmpp/sasl.go | 43 +++---------------------------------------- xmpp/streams.go | 26 -------------------------- 2 files changed, 3 insertions(+), 66 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/sasl.go b/xmpp/sasl.go index 5fac934..ae3be4a 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -10,12 +10,6 @@ type saslRequest struct { Payload []byte `xml:",chardata"` } -type saslSuccess struct {} - -func handleSaslSuccess(s *session) { - openStream(s.tx, s.jid) -} - func (s *session) sasl() { start := xml.StartElement{ xml.Name{"urn:ietf:params:xml:ns:xmpp-sasl", "auth"}, @@ -35,39 +29,8 @@ func (s *session) sasl() { } } -// hasSaslPlain scans the given stream features XML element for the SASL PLAIN -// mechanism which is supported by xengineering.eu/limox/xmpp. It returns true -// if the stream has support for this mechanism and false otherwise. -func hasSaslPlain(e []xml.Token) bool { - mechanism := xml.Name{`urn:ietf:params:xml:ns:xmpp-sasl`, `mechanism`} - - for i, t := range e { - switch s := t.(type) { - case xml.StartElement: - if s.Name == mechanism { - if i+1 < len(e) { - subtype := func() string { - switch c := e[i+1].(type) { - case xml.CharData: - return string(c) - default: - return "" - } - }() - if subtype == `PLAIN` { - return true - } - } - } - } - } - - return false -} - -func saslSuccessHandler(s *session, e []xml.Token) { -} +type saslSuccess struct {} -func saslFailureHandler(s *session, e []xml.Token) { - log.Println("SASL autentication failed!") +func handleSaslSuccess(s *session) { + openStream(s.tx, s.jid) } diff --git a/xmpp/streams.go b/xmpp/streams.go index 18a5e6a..ec16a02 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -11,8 +11,6 @@ type streamFeatures struct { } func handleStreamFeatures(s *session, f streamFeatures) { - log.Print(f) - if len(f.SaslMechanisms) > 0 { for _, v := range f.SaslMechanisms { if v == "PLAIN" { @@ -64,27 +62,3 @@ func closeStream(e *xml.Encoder) { log.Println("Could not flush after stream end!") } } - -func iqHandler(s *session, e []xml.Token) { - isResult := false - idMatches := false - - result := xml.Attr{xml.Name{"", "type"}, "result"} - id := xml.Attr{xml.Name{"", "id"}, s.resourceReq} - - switch start := e[0].(type) { - case xml.StartElement: - for _, v := range start.Attr { - if v == result { - isResult = true - } - if v == id { - idMatches = true - } - } - - if isResult && idMatches { - s.sendPresence() - } - } -} -- cgit v1.2.3-70-g09d2 From d9fe0a4360770b1e4b6b4fb3686c3275ad1b6e6e Mon Sep 17 00:00:00 2001 From: xengineering Date: Tue, 4 Jul 2023 22:09:36 +0200 Subject: Apply go fmt --- xmpp/jid.go | 2 +- xmpp/sasl.go | 2 +- xmpp/session.go | 2 +- xmpp/streams.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'xmpp/streams.go') diff --git a/xmpp/jid.go b/xmpp/jid.go index c732027..9580ad5 100644 --- a/xmpp/jid.go +++ b/xmpp/jid.go @@ -43,7 +43,7 @@ func username(jid string) string { type bindRequest struct { Bind struct { - Xmlns string `xml:"xmlns,attr"` + Xmlns string `xml:"xmlns,attr"` Resource struct { Content string `xml:",chardata"` } `xml:"resource"` diff --git a/xmpp/sasl.go b/xmpp/sasl.go index ae3be4a..69b536d 100644 --- a/xmpp/sasl.go +++ b/xmpp/sasl.go @@ -29,7 +29,7 @@ func (s *session) sasl() { } } -type saslSuccess struct {} +type saslSuccess struct{} func handleSaslSuccess(s *session) { openStream(s.tx, s.jid) diff --git a/xmpp/session.go b/xmpp/session.go index 7a07280..4dfd76f 100644 --- a/xmpp/session.go +++ b/xmpp/session.go @@ -58,7 +58,7 @@ func (s *session) run() { openStream(s.tx, s.jid) defer closeStream(s.tx) - s.out <- SessionConnect{} // TODO this should be sent after initial presence + s.out <- SessionConnect{} // TODO this should be sent after initial presence defer func() { s.out <- SessionDisconnect{} }() for { diff --git a/xmpp/streams.go b/xmpp/streams.go index ec16a02..9f6ffe8 100644 --- a/xmpp/streams.go +++ b/xmpp/streams.go @@ -7,7 +7,7 @@ import ( type streamFeatures struct { SaslMechanisms []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms>mechanism"` - Bind *bool `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` + Bind *bool `xml:"urn:ietf:params:xml:ns:xmpp-bind bind,omitempty"` } func handleStreamFeatures(s *session, f streamFeatures) { -- cgit v1.2.3-70-g09d2