package xmpp import ( "crypto/tls" "encoding/xml" "errors" "fmt" "log" ) // Config structure used to create a new XMPP client connection. type ClientConfig struct { // Don't upgrade the connection to TLS, even if the server supports it. If // the server *requires* TLS then this option is ignored. NoTLS bool // Skip verification of the server's certificate chain. Probably only // useful during development. InsecureSkipVerify bool } // Create a client XMPP over the stream. func NewClientXMPP(stream *Stream, jid JID, password string, config *ClientConfig) (*XMPP, error) { for { if err := startClient(stream, jid); err != nil { return nil, err } // Read features. f := new(features) if err := stream.Decode(f); err != nil { return nil, err } // TLS? if f.StartTLS != nil && (f.StartTLS.Required != nil || !config.NoTLS) { log.Println("Start TLS") if err := startTLS(stream, config); err != nil { return nil, err } continue // Restart } // Authentication if f.Mechanisms != nil { log.Println("Authenticating") if err := authenticate(stream, f.Mechanisms.Mechanisms, jid.Node, password); err != nil { return nil, err } continue // Restart } // Bind resource. if f.Bind != nil { log.Println("Binding resource.") boundJID, err := bindResource(stream, jid) if err != nil { return nil, err } jid = boundJID } break } return newXMPP(jid, stream), nil } func startClient(stream *Stream, jid JID) error { start := xml.StartElement{ xml.Name{"stream", "stream"}, []xml.Attr{ xml.Attr{xml.Name{"", "xmlns"}, "jabber:client"}, xml.Attr{xml.Name{"xmlns", "stream"}, "http://etherx.jabber.org/streams"}, xml.Attr{xml.Name{"", "from"}, jid.Full()}, xml.Attr{xml.Name{"", "to"}, jid.Domain}, }, } if err := stream.SendStart(&start); err != nil { return err } if _, err := stream.Next(&xml.Name{nsStream, "stream"}); err != nil { return err } return nil } func startTLS(stream *Stream, config *ClientConfig) error { if err := stream.Send(&tlsStart{}); err != nil { return err } p := tlsProceed{} if err := stream.Decode(&p); err != nil { return err } tlsConfig := tls.Config{InsecureSkipVerify: config.InsecureSkipVerify} return stream.UpgradeTLS(&tlsConfig) } type tlsStart struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` } type tlsProceed struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"` } func authenticate(stream *Stream, mechanisms []string, user, password string) error { log.Println("authenticate, mechanisms=", mechanisms) if !stringSliceContains(mechanisms, "PLAIN") { return errors.New("Only PLAIN supported for now") } return authenticatePlain(stream, user, password) } func authenticatePlain(stream *Stream, user, password string) error { auth := saslAuth{Mechanism: "PLAIN", Message: saslEncodePlain(user, password)} if err := stream.Send(&auth); err != nil { return err } if se, err := stream.Next(nil); err != nil { return err } else { if se.Name.Local == "failure" { f := new(saslFailure) stream.DecodeElement(f, se) return errors.New(fmt.Sprintf("Authentication failed: %s", f.Reason.Local)) } } return nil } type saslAuth struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"` Mechanism string `xml:"mechanism,attr"` Message string `xml:",chardata"` } func bindResource(stream *Stream, jid JID) (JID, error) { if jid.Resource == "" { return bindResourceServer(stream) } return bindResourceClient(stream, jid) } func bindResourceClient(stream *Stream, jid JID) (JID, error) { req := Iq{Id: "foo", Type: "set"} req.PayloadEncode(bindIq{Resource: jid.Resource}) if err := stream.Send(req); err != nil { return JID{}, err } resp := Iq{} err := stream.Decode(&resp) if err != nil { return JID{}, err } bindResp := bindIq{} resp.PayloadDecode(&bindResp) boundJID, err := ParseJID(bindResp.JID) return boundJID, nil } func bindResourceServer(stream *Stream) (JID, error) { panic("bindResourceServer not implemented") } type bindIq struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` Resource string `xml:"resource,omitempty"` JID string `xml:"jid,omitempty"` } func stringSliceContains(l []string, m string) bool { for _, i := range l { if i == m { return true } } return false } type features struct { XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"` StartTLS *tlsStartTLS `xml:"starttls"` Mechanisms *mechanisms `xml:"mechanisms"` Bind *bind `xml:"bind"` } type bind struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` Required *required `xml:"required"` } type mechanisms struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` Mechanisms []string `xml:"mechanism"` } type tlsStartTLS struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` Required *required `xml:"required"` } type required struct {} type saslSuccess struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"` } type saslFailure struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"` Reason xml.Name `xml:",any"` } // BUG(matt): Implement server-side resource binding. // BUG(matt): Don't use "foo" as the id during resource binding.