sum7
/
yaja
Archived
1
0
Fork 0

improve client

This commit is contained in:
Martin/Geno 2018-02-11 19:35:32 +01:00
parent f4bc539cd7
commit 654d0306cf
No known key found for this signature in database
GPG Key ID: F0D39A37E925E941
14 changed files with 232 additions and 146 deletions

View File

@ -19,25 +19,27 @@ func (client *Client) auth(password string) error {
}
//auth:
mechanism := ""
challenge := &messages.SASLChallenge{}
response := &messages.SASLResponse{}
for _, m := range f.Mechanisms.Mechanism {
if m == "PLAIN" {
if m == "SCRAM-SHA-1" {
/*
mechanism = m
// Plain authentication: send base64-encoded \x00 user \x00 password.
raw := "\x00" + client.JID.Local + "\x00" + password
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
fmt.Fprintf(client.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", messages.NSSASL, enc)
TODO
break
*/
}
if m == "DIGEST-MD5" {
mechanism = m
// Digest-MD5 authentication
fmt.Fprintf(client.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", messages.NSSASL)
var ch string
if err := client.ReadElement(&ch); err != nil {
client.Out.Encode(&messages.SASLAuth{
Mechanism: m,
})
if err := client.ReadElement(challenge); err != nil {
return err
}
b, err := base64.StdEncoding.DecodeString(string(ch))
b, err := base64.StdEncoding.DecodeString(challenge.Body)
if err != nil {
return err
}
@ -62,29 +64,37 @@ func (client *Client) auth(password string) error {
message := "username=\"" + client.JID.Local + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
fmt.Fprintf(client.conn, "<response xmlns='%s'>%s</response>\n", messages.NSSASL, base64.StdEncoding.EncodeToString([]byte(message)))
response.Body = base64.StdEncoding.EncodeToString([]byte(message))
client.Out.Encode(response)
break
}
if m == "PLAIN" {
mechanism = m
// Plain authentication: send base64-encoded \x00 user \x00 password.
raw := "\x00" + client.JID.Local + "\x00" + password
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
client.Out.Encode(&messages.SASLAuth{
Mechanism: "PLAIN",
Body: string(enc),
})
err = client.ReadElement(&ch)
if err != nil {
return err
}
_, err = base64.StdEncoding.DecodeString(ch)
if err != nil {
return err
}
fmt.Fprintf(client.conn, "<response xmlns='%s'/>\n", messages.NSSASL)
break
}
}
if mechanism == "" {
return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)
return fmt.Errorf("PLAIN authentication is not an option: %s", f.Mechanisms.Mechanism)
}
element, err := client.Read()
if err != nil {
return err
}
if element.Name.Local != "success" {
return errors.New("auth failed: " + element.Name.Local)
fail := messages.SASLFailure{}
if err := client.In.DecodeElement(&fail, element); err == nil {
return errors.New(messages.XMLChildrenString(fail) + " : " + fail.Body)
}
if err := client.In.DecodeElement(&messages.SASLSuccess{}, element); err != nil {
return errors.New("auth failed - with unexpected answer")
}
return nil
}

View File

@ -11,7 +11,6 @@ import (
"dev.sum7.eu/genofire/yaja/messages"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
// Client holds XMPP connection opitons
@ -24,7 +23,7 @@ type Client struct {
}
func NewClient(jid *model.JID, password string) (*Client, error) {
return NewClientProtocolDuration(jid, password, "tcp", -1)
return NewClientProtocolDuration(jid, password, "tcp", 0)
}
func NewClientProtocolDuration(jid *model.JID, password string, proto string, timeout time.Duration) (*Client, error) {
@ -43,12 +42,7 @@ func NewClientProtocolDuration(jid *model.JID, password string, proto string, ti
if len(a) == 1 {
addr += ":5222"
}
var conn net.Conn
if timeout >= 0 {
conn, err = net.DialTimeout(proto, addr, timeout)
} else {
conn, err = net.Dial(proto, addr)
}
conn, err := net.DialTimeout(proto, addr, timeout)
if err != nil {
return nil, err
}
@ -124,12 +118,11 @@ func (client *Client) connect(password string) error {
if err := tlsconn.VerifyHostname(client.JID.Domain); err != nil {
return err
}
err := client.auth(password)
if err != nil {
if err := client.auth(password); err != nil {
return err
}
_, err = client.startStream()
if err != nil {
if _, err := client.startStream(); err != nil {
return err
}
// bind to resource
@ -137,23 +130,22 @@ func (client *Client) connect(password string) error {
if client.JID.Resource != "" {
bind.Resource = client.JID.Resource
}
client.Out.Encode(&messages.IQClient{
if err := client.Out.Encode(&messages.IQClient{
Type: messages.IQTypeSet,
To: model.NewJID(client.JID.Domain),
From: client.JID,
ID: utils.CreateCookieString(),
To: model.NewJID(client.JID.Domain),
Bind: bind,
})
}); err != nil {
return err
}
var iq messages.IQClient
if err := client.ReadElement(&iq); err != nil {
return err
}
if iq.Error != nil {
if iq.Error.Type == messages.ErrorClientTypeCancel && iq.Error.ServiceUnavailable != nil {
//TODO binding service unavailable
} else {
return errors.New(fmt.Sprintf("recv error on iq>bind: %s[%s]: %s -> %v", iq.Error.Code, iq.Error.Type, iq.Error.Text, iq.Error.Other))
if iq.Error.ServiceUnavailable == nil {
return errors.New(fmt.Sprintf("recv error on iq>bind: %s[%s]: %s -> %s -> %s", iq.Error.Code, iq.Error.Type, iq.Error.Text, messages.XMLChildrenString(iq.Error.StanzaErrorGroup), messages.XMLChildrenString(iq.Error.Other)))
}
} else if iq.Bind == nil {
return errors.New("<iq> result missing <bind>")
@ -162,10 +154,8 @@ func (client *Client) connect(password string) error {
client.JID.Domain = iq.Bind.JID.Domain
client.JID.Resource = iq.Bind.JID.Resource
} else {
return errors.New(fmt.Sprintf("%v", iq.Other))
return errors.New(messages.XMLChildrenString(iq.Other))
}
// set status
err = client.Send(&messages.PresenceClient{Show: messages.ShowTypeXA, Status: "online"})
return err
return client.Send(&messages.PresenceClient{Show: messages.PresenceShowXA, Status: "online"})
}

View File

@ -5,7 +5,6 @@ import (
"log"
"dev.sum7.eu/genofire/yaja/messages"
"dev.sum7.eu/genofire/yaja/server/utils"
)
func (client *Client) Read() (*xml.StartElement, error) {
@ -51,25 +50,16 @@ func (client *Client) Send(p interface{}) error {
msg, ok := p.(*messages.MessageClient)
if ok {
msg.From = client.JID
if msg.ID == "" {
msg.ID = utils.CreateCookieString()
}
return client.Out.Encode(msg)
}
iq, ok := p.(*messages.IQClient)
if ok {
iq.From = client.JID
if iq.ID == "" {
iq.ID = utils.CreateCookieString()
}
return client.Out.Encode(iq)
}
pc, ok := p.(*messages.PresenceClient)
if ok {
pc.From = client.JID
if pc.ID == "" {
pc.ID = utils.CreateCookieString()
}
return client.Out.Encode(pc)
}
return client.Out.Encode(p)

View File

@ -6,7 +6,6 @@ import (
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/yaja/messages"
"dev.sum7.eu/genofire/yaja/server/utils"
)
func (t *Tester) StartBot(status *Status) {
@ -24,7 +23,7 @@ func (t *Tester) StartBot(status *Status) {
errMSG := &messages.StreamError{}
err = status.client.In.DecodeElement(errMSG, element)
if err == nil {
logCTX.Errorf("recv stream error: %s: %v", errMSG.Text, errMSG.Any)
logCTX.Errorf("recv stream error: %s: %s", errMSG.Text, messages.XMLChildrenString(errMSG.Any))
status.client.Close()
status.Login = false
return
@ -40,7 +39,7 @@ func (t *Tester) StartBot(status *Status) {
iq.From = status.client.JID
status.client.Out.Encode(iq)
} else {
logCTX.Warnf("unsupport iq recv: %v", iq)
logCTX.Warnf("recv iq unsupport: %s", messages.XMLChildrenString(iq))
}
continue
}
@ -51,7 +50,7 @@ func (t *Tester) StartBot(status *Status) {
sender := pres.From
logPres := logCTX.WithField("from", sender.Full())
if pres.Type == messages.PresenceTypeSubscribe {
logPres.Debugf("recv subscribe")
logPres.Debugf("recv presence subscribe")
pres.Type = messages.PresenceTypeSubscribed
pres.To = sender
pres.From = nil
@ -59,17 +58,19 @@ func (t *Tester) StartBot(status *Status) {
logPres.Debugf("accept new subscribe")
pres.Type = messages.PresenceTypeSubscribe
pres.ID = utils.CreateCookieString()
pres.ID = ""
status.client.Out.Encode(pres)
logPres.Info("request also subscribe")
} else if pres.Type == messages.PresenceTypeSubscribed {
logPres.Info("recv accepted subscribe")
logPres.Info("recv presence accepted subscribe")
} else if pres.Type == messages.PresenceTypeUnsubscribe {
logPres.Info("recv remove subscribe")
logPres.Info("recv presence remove subscribe")
} else if pres.Type == messages.PresenceTypeUnsubscribed {
logPres.Info("recv removed subscribe")
logPres.Info("recv presence removed subscribe")
} else if pres.Type == messages.PresenceTypeUnavailable {
logPres.Debug("recv presence unavailable")
} else {
logCTX.Warnf("unsupported presence recv: %v", pres)
logCTX.Warnf("recv presence unsupported: %s -> %s", pres.Type, messages.XMLChildrenString(pres))
}
continue
}
@ -82,7 +83,13 @@ func (t *Tester) StartBot(status *Status) {
}
logCTX = logCTX.WithField("from", msg.From.Full()).WithField("msg-recv", msg.Body)
if msg.Error != nil {
logCTX.Debugf("recv msg with error %s[%s]: %s -> %v -> %v", msg.Error.Code, msg.Error.Type, msg.Error.Text, msg.Error.StanzaErrorGroup, msg.Error.Other)
if msg.Error.Type == "auth" {
logCTX.Warnf("recv msg with error not auth")
status.Login = false
status.client.Close()
return
}
logCTX.Debugf("recv msg with error %s[%s]: %s -> %s -> %s", msg.Error.Code, msg.Error.Type, msg.Error.Text, messages.XMLChildrenString(msg.Error.StanzaErrorGroup), messages.XMLChildrenString(msg.Error.Other))
continue
}

View File

@ -9,7 +9,6 @@ import (
"dev.sum7.eu/genofire/yaja/client"
"dev.sum7.eu/genofire/yaja/messages"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type Tester struct {
@ -149,12 +148,12 @@ func (t *Tester) CheckStatus() {
logCTXTo.Debug("could not recv msg")
}
}
msg = utils.CreateCookieString()
msg = messages.CreateCookieString()
logCTXTo = logCTXTo.WithField("msg-send", msg)
own.client.Send(&messages.MessageClient{
Body: "checkmsg " + msg,
Type: messages.ChatTypeChat,
Type: messages.MessageTypeChat,
To: s.JID,
})
own.MessageForConnection[s.JID.Bare()] = msg

35
messages/message.go Normal file
View File

@ -0,0 +1,35 @@
package messages
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/model"
)
type MessageType string
const (
MessageTypeChat MessageType = "chat"
MessageTypeGroupchat MessageType = "groupchat"
MessageTypeError MessageType = "error"
MessageTypeHeadline MessageType = "headline"
MessageTypeNormal MessageType = "normal"
)
// MessageClient element
type MessageClient struct {
XMLName xml.Name `xml:"jabber:client message"`
From *model.JID `xml:"from,attr,omitempty"`
ID string `xml:"id,attr,omitempty"`
To *model.JID `xml:"to,attr,omitempty"`
Type MessageType `xml:"type,attr,omitempty"`
Lang string `xml:"lang,attr,omitempty"`
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
// Any hasn't matched element
Other []XMLElement `xml:",any"`
Delay *Delay `xml:"delay"`
Error *ErrorClient
}

View File

@ -6,15 +6,6 @@ import (
"dev.sum7.eu/genofire/yaja/model"
)
type XMLElement struct {
XMLName xml.Name
InnerXML string `xml:",innerxml"`
}
type Delay struct {
Stamp string `xml:"stamp,attr"`
}
type PresenceType string
const (
@ -27,13 +18,13 @@ const (
PresenceTypeError PresenceType = "error"
)
type ShowType string
type PresenceShow string
const (
ShowTypeAway ShowType = "away"
ShowTypeChat ShowType = "chat"
ShowTypeDND ShowType = "dnd"
ShowTypeXA ShowType = "xa"
PresenceShowAway PresenceShow = "away"
PresenceShowChat PresenceShow = "chat"
PresenceShowDND PresenceShow = "dnd"
PresenceShowXA PresenceShow = "xa"
)
// PresenceClient element
@ -45,7 +36,7 @@ type PresenceClient struct {
Type PresenceType `xml:"type,attr,omitempty"`
Lang string `xml:"lang,attr,omitempty"`
Show ShowType `xml:"show,omitempty"` // away, chat, dnd, xa
Show PresenceShow `xml:"show,omitempty"` // away, chat, dnd, xa
Status string `xml:"status,omitempty"` // sb []clientText
Priority string `xml:"priority,omitempty"`
// Caps *ClientCaps `xml:"c"`
@ -53,31 +44,3 @@ type PresenceClient struct {
Error *ErrorClient
}
type ChatType string
const (
ChatTypeChat ChatType = "chat"
ChatTypeGroupchat ChatType = "groupchat"
ChatTypeError ChatType = "error"
ChatTypeHeadline ChatType = "headline"
ChatTypeNormal ChatType = "normal"
)
// MessageClient element
type MessageClient struct {
XMLName xml.Name `xml:"jabber:client message"`
From *model.JID `xml:"from,attr,omitempty"`
ID string `xml:"id,attr,omitempty"`
To *model.JID `xml:"to,attr,omitempty"`
Type ChatType `xml:"type,attr,omitempty"`
Lang string `xml:"lang,attr,omitempty"`
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
// Any hasn't matched element
Other []XMLElement `xml:",any"`
Delay *Delay `xml:"delay"`
Error *ErrorClient
}

View File

@ -1,6 +1,14 @@
package messages
import "encoding/xml"
import (
"encoding/xml"
)
// RFC 3920 C.4 SASL name space
type SASLMechanisms struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
Mechanism []string `xml:"mechanism"`
}
// SASLAuth element
type SASLAuth struct {
@ -9,8 +17,44 @@ type SASLAuth struct {
Body string `xml:",chardata"`
}
// RFC 3920 C.4 SASL name space
type SASLMechanisms struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
Mechanism []string `xml:"mechanism"`
// SASLChallenge element
type SASLChallenge struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl challenge"`
Body string `xml:",chardata"`
}
// SASLResponse element
type SASLResponse struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl response"`
Body string `xml:",chardata"`
}
// SASLSuccess element
type SASLSuccess struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
Body string `xml:",chardata"`
}
// SASLAbout element
type SASLAbout struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"`
}
// SASLFailure element
type SASLFailure struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
Aborted *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl aborted"`
AccountDisabled *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl account-disabled"`
CredentialsExpired *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl credentials-expired"`
EncryptionRequired *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl encryption-required"`
IncorrectEncoding *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding"`
InvalidAuthzid *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid"`
InvalidMechanism *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism"`
MalformedRequest *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl malformed-request"`
MechanismTooWeak *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak"`
NotAuthorized *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl not-authorized"`
TemporaryAuthFailure *xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure"`
Body string `xml:",chardata"`
}

66
messages/utils.go Normal file
View File

@ -0,0 +1,66 @@
package messages
import (
"crypto/rand"
"encoding/binary"
"encoding/xml"
"fmt"
"reflect"
)
type Delay struct {
Stamp string `xml:"stamp,attr"`
}
type XMLElement struct {
XMLName xml.Name
InnerXML string `xml:",innerxml"`
}
func XMLChildrenString(o interface{}) (result string) {
first := true
val := reflect.ValueOf(o)
if val.Kind() == reflect.Interface && !val.IsNil() {
elm := val.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
val = elm
}
}
if val.Kind() != reflect.Struct {
return
}
// struct
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
elm := valueField.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
valueField = elm
}
}
if xmlElement, ok := valueField.Interface().(*xml.Name); ok && xmlElement != nil {
if first {
first = false
} else {
result += ", "
}
result += xmlElement.Local
}
}
return
}
// Cookie is used to give a unique identifier to each request.
type Cookie uint64
func CreateCookie() Cookie {
var buf [8]byte
if _, err := rand.Reader.Read(buf[:]); err != nil {
panic("Failed to read random bytes: " + err.Error())
}
return Cookie(binary.LittleEndian.Uint64(buf[:]))
}
func CreateCookieString() string {
return fmt.Sprintf("%x", CreateCookie())
}

View File

@ -51,7 +51,7 @@ func (iex IQExtensions) Process(element *xml.StartElement, client *utils.Client)
// not extensions found
if count != 1 {
log.Debugf("%s - %s: %v", msg.XMLName.Space, msg.Type, msg.Other)
log.Debugf("%s - %s: %s", msg.XMLName.Space, msg.Type, messages.XMLChildrenString(msg.Other))
}
return true

View File

@ -44,7 +44,7 @@ func (state *Start) Process() State {
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
<stream:stream id='%x' version='1.0' xmlns='%s' xmlns:stream='%s'>`,
utils.CreateCookie(), messages.NSClient, messages.NSStream)
messages.CreateCookie(), messages.NSClient, messages.NSStream)
fmt.Fprintf(state.Client.Conn, `<stream:features>
<starttls xmlns='%s'>

View File

@ -73,7 +73,7 @@ func (state *TLSStream) Process() state.State {
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>`,
utils.CreateCookie(), messages.NSClient, messages.NSStream,
messages.CreateCookie(), messages.NSClient, messages.NSStream,
messages.NSSASL, messages.NSFeaturesIQRegister)
} else {
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
@ -83,7 +83,7 @@ func (state *TLSStream) Process() state.State {
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>`,
utils.CreateCookie(), messages.NSClient, messages.NSStream,
messages.CreateCookie(), messages.NSClient, messages.NSStream,
messages.NSSASL)
}
@ -174,7 +174,7 @@ func (state *AuthedStart) Process() state.State {
<required/>
</bind>
</stream:features>`,
messages.NSStream, state.Client.JID.Domain, utils.CreateCookie(), messages.NSClient,
messages.NSStream, state.Client.JID.Domain, messages.CreateCookie(), messages.NSClient,
messages.NSBind)
return state.Next

View File

@ -97,7 +97,7 @@ func (state *TLSStream) Process() state.State {
</mechanisms>
<bidi xmlns='urn:xmpp:features:bidi'/>
</stream:features>`,
utils.CreateCookie(), messages.NSClient, messages.NSStream,
messages.CreateCookie(), messages.NSClient, messages.NSStream,
messages.NSSASL)
return state.Next

View File

@ -1,25 +1,7 @@
package utils
import (
"crypto/rand"
"encoding/binary"
"fmt"
"dev.sum7.eu/genofire/yaja/model"
)
// Cookie is used to give a unique identifier to each request.
type Cookie uint64
func CreateCookie() Cookie {
var buf [8]byte
if _, err := rand.Reader.Read(buf[:]); err != nil {
panic("Failed to read random bytes: " + err.Error())
}
return Cookie(binary.LittleEndian.Uint64(buf[:]))
}
func CreateCookieString() string {
return fmt.Sprintf("%x", CreateCookie())
}
type DomainRegisterAllowed func(*model.JID) bool