@@ 4,10 4,24 @@
package ibr2
+import (
+ "context"
+ "encoding/xml"
+)
+
// Challenge is an IBR challenge.
-// API WARNING: The challenge struct is not complete or usable yet.
type Challenge struct {
// Type is the type of the challenge as it appears in the server advertised
// challenges list.
Type string
+
+ // Send is used by the server to send the challenge to the client.
+ Send func(ctx context.Context, e *xml.Encoder) error
+
+ // Respond is used by the client to send a reponse or reply to the challenge.
+ Respond func(context.Context, *xml.Encoder) error
+
+ // Receive is used by the client to receive and decode the server's challenge
+ // and by the server to receive and decode the clients response.
+ Receive func(ctx context.Context, server bool, d *xml.Decoder, start *xml.StartElement) error
}
@@ 16,6 16,7 @@ import (
"io"
"mellium.im/xmpp"
+ "mellium.im/xmpp/streamerror"
)
// Namespaces used by IBR.
@@ 27,6 28,21 @@ var (
errNoChallenge = errors.New("No supported challenges were found")
)
+func challengeStart(typ string) xml.StartElement {
+ return xml.StartElement{
+ Name: xml.Name{
+ Space: NS,
+ Local: "challenge",
+ },
+ Attr: []xml.Attr{
+ {
+ Name: xml.Name{Local: "type"},
+ Value: typ,
+ },
+ },
+ }
+}
+
func listFunc(challenges ...Challenge) func(context.Context, *xml.Encoder, xml.StartElement) (bool, error) {
return func(ctx context.Context, e *xml.Encoder, start xml.StartElement) (req bool, err error) {
if err = e.EncodeToken(start); err != nil {
@@ 89,6 105,31 @@ func parseFunc(challenges ...Challenge) func(ctx context.Context, d *xml.Decoder
}
}
+func decodeClientResp(ctx context.Context, d *xml.Decoder, decode func(ctx context.Context, server bool, d *xml.Decoder, start *xml.StartElement) error) (cancel bool, err error) {
+ var tok xml.Token
+ tok, err = d.Token()
+ if err != nil {
+ return
+ }
+ start, ok := tok.(xml.StartElement)
+ switch {
+ case !ok:
+ err = streamerror.RestrictedXML
+ return
+ case start.Name.Local == "cancel" && start.Name.Space == NS:
+ cancel = true
+ return
+ case start.Name.Local == "response" && start.Name.Space == NS:
+ err = decode(ctx, true, d, &start)
+ if err != nil {
+ return
+ }
+ }
+
+ err = streamerror.BadFormat
+ return
+}
+
func negotiateFunc(challenges ...Challenge) func(context.Context, *xmpp.Session, interface{}) (xmpp.SessionState, io.ReadWriter, error) {
return func(ctx context.Context, session *xmpp.Session, supported interface{}) (mask xmpp.SessionState, rw io.ReadWriter, err error) {
server := (session.State() & xmpp.Received) == xmpp.Received
@@ 100,8 141,88 @@ func negotiateFunc(challenges ...Challenge) func(context.Context, *xmpp.Session,
return
}
- // TODO:
- panic("not yet supported")
+ var tok xml.Token
+ e := session.Encoder()
+ d := session.Decoder()
+
+ if server {
+ for _, c := range challenges {
+ // Send the challenge.
+ start := challengeStart(c.Type)
+ err = e.EncodeToken(start)
+ if err != nil {
+ return
+ }
+ err = c.Send(ctx, e)
+ if err != nil {
+ return
+ }
+ err = e.EncodeToken(start.End())
+ if err != nil {
+ return
+ }
+ err = e.Flush()
+ if err != nil {
+ return
+ }
+
+ // Decode the clients response
+ var cancel bool
+ cancel, err = decodeClientResp(ctx, d, c.Receive)
+ if err != nil || cancel {
+ return
+ }
+ }
+ return
+ }
+
+ // If we're the client, decode the challenge.
+ tok, err = d.Token()
+ if err != nil {
+ return
+ }
+ start, ok := tok.(xml.StartElement)
+ switch {
+ case !ok:
+ err = streamerror.RestrictedXML
+ return
+ case start.Name.Local != "challenge" || start.Name.Space != NS:
+ err = streamerror.BadFormat
+ return
+ }
+ var typ string
+ for _, attr := range start.Attr {
+ if attr.Name.Local == "type" {
+ typ = attr.Value
+ break
+ }
+ }
+ // If there was no type attr, an illegal challenge was sent.
+ if typ == "" {
+ err = streamerror.BadFormat
+ return
+ }
+
+ for _, c := range challenges {
+ if c.Type != typ {
+ continue
+ }
+
+ err = c.Receive(ctx, false, d, &start)
+ if err != nil {
+ return
+ }
+
+ if c.Respond != nil {
+ err = c.Respond(ctx, e)
+ if err != nil {
+ return
+ }
+ }
+
+ break
+ }
+ return
}
}
@@ 5,6 5,9 @@
package ibr2
import (
+ "context"
+ "encoding/xml"
+
"mellium.im/xmpp/oob"
)
@@ 13,10 16,12 @@ import (
// If you are a client, f will be called and passed the parsed OOB data.
// If f returns an error, the client considers the negotiation a failure.
// The returned OOB data is ignored for clients.
-// For servers, f is also called, but its argument should be ignored and the
-// returned OOB data should be sent on the connection (error is also checked).
+// For servers, f is also called.
func OOB(f func(*oob.Data) (*oob.Data, error)) Challenge {
return Challenge{
Type: oob.NS,
+ Send: func(ctx context.Context, e *xml.Encoder) error {
+ return nil
+ },
}
}