diff --git a/client/auth.go b/client/auth.go new file mode 100644 index 0000000..f2bc9fb --- /dev/null +++ b/client/auth.go @@ -0,0 +1,119 @@ +package client + +import ( + "crypto/md5" + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "math/big" + "strings" + + "dev.sum7.eu/genofire/yaja/messages" +) + +func (client *Client) auth(password string) error { + f, err := client.startStream() + if err != nil { + return err + } + //auth: + mechanism := "" + for _, m := range f.Mechanisms.Mechanism { + 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)) + fmt.Fprintf(client.conn, "%s\n", messages.NSSASL, enc) + break + } + if m == "DIGEST-MD5" { + mechanism = m + // Digest-MD5 authentication + fmt.Fprintf(client.conn, "\n", messages.NSSASL) + var ch string + if err := client.ReadElement(&ch); err != nil { + return err + } + b, err := base64.StdEncoding.DecodeString(string(ch)) + if err != nil { + return err + } + tokens := map[string]string{} + for _, token := range strings.Split(string(b), ",") { + kv := strings.SplitN(strings.TrimSpace(token), "=", 2) + if len(kv) == 2 { + if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { + kv[1] = kv[1][1 : len(kv[1])-1] + } + tokens[kv[0]] = kv[1] + } + } + realm, _ := tokens["realm"] + nonce, _ := tokens["nonce"] + qop, _ := tokens["qop"] + charset, _ := tokens["charset"] + cnonceStr := cnonce() + digestURI := "xmpp/" + client.JID.Domain + nonceCount := fmt.Sprintf("%08x", 1) + digest := saslDigestResponse(client.JID.Local, realm, password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount) + 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, "%s\n", messages.NSSASL, base64.StdEncoding.EncodeToString([]byte(message))) + + err = client.ReadElement(&ch) + if err != nil { + return err + } + _, err = base64.StdEncoding.DecodeString(ch) + if err != nil { + return err + } + fmt.Fprintf(client.conn, "\n", messages.NSSASL) + break + } + } + if mechanism == "" { + return fmt.Errorf("PLAIN authentication is not an option: %v", 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) + } + return nil +} + +func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string { + h := func(text string) []byte { + h := md5.New() + h.Write([]byte(text)) + return h.Sum(nil) + } + hex := func(bytes []byte) string { + return fmt.Sprintf("%x", bytes) + } + kd := func(secret, data string) []byte { + return h(secret + ":" + data) + } + + a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr + a2 := authenticate + ":" + digestURI + response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2)))) + return response +} + +func cnonce() string { + randSize := big.NewInt(0) + randSize.Lsh(big.NewInt(1), 64) + cn, err := rand.Int(rand.Reader, randSize) + if err != nil { + return "" + } + return fmt.Sprintf("%016x", cn) +} diff --git a/client/client.go b/client/client.go index 3a1eb30..9669251 100644 --- a/client/client.go +++ b/client/client.go @@ -1,14 +1,10 @@ package client import ( - "crypto/md5" - "crypto/rand" "crypto/tls" - "encoding/base64" "encoding/xml" "errors" "fmt" - "math/big" "net" "strings" @@ -26,8 +22,23 @@ type Client struct { JID *model.JID } -func NewClient(jid model.JID, password string) (*Client, error) { - conn, err := net.Dial("tcp", jid.Domain+":5222") +func NewClient(jid *model.JID, password string) (*Client, error) { + _, srvEntries, err := net.LookupSRV("xmpp-client", "tcp", jid.Domain) + addr := jid.Domain + ":5222" + if err == nil && len(srvEntries) > 0 { + bestSrv := srvEntries[0] + for _, srv := range srvEntries { + if srv.Priority <= bestSrv.Priority && srv.Weight >= bestSrv.Weight { + bestSrv = srv + addr = fmt.Sprintf("%s:%d", srv.Target, srv.Port) + } + } + } + a := strings.SplitN(addr, ":", 2) + if len(a) == 1 { + addr += ":5222" + } + conn, err := net.Dial("tcp", addr) if err != nil { return nil, err } @@ -36,7 +47,7 @@ func NewClient(jid model.JID, password string) (*Client, error) { In: xml.NewDecoder(conn), Out: xml.NewEncoder(conn), - JID: &jid, + JID: jid, } if err = client.connect(password); err != nil { @@ -54,51 +65,31 @@ func (c *Client) Close() error { return nil } -func (client *Client) Read() (*xml.StartElement, error) { - for { - nextToken, err := client.In.Token() - if err != nil { - return nil, err - } - switch nextToken.(type) { - case xml.StartElement: - element := nextToken.(xml.StartElement) - return &element, nil - } - } -} -func (client *Client) ReadElement(p interface{}) error { - element, err := client.Read() - if err != nil { - return err - } - return client.In.DecodeElement(p, element) -} - -func (client *Client) init() error { +func (client *Client) startStream() (*messages.StreamFeatures, error) { // XMPP-Connection _, err := fmt.Fprintf(client.conn, "\n"+ "\n", model.XMLEscape(client.JID.Domain), messages.NSClient, messages.NSStream) if err != nil { - return err + return nil, err } element, err := client.Read() if err != nil { - return err + return nil, err } if element.Name.Space != messages.NSStream || element.Name.Local != "stream" { - return errors.New("is not stream") + return nil, errors.New("is not stream") } - return nil + f := &messages.StreamFeatures{} + if err := client.ReadElement(f); err != nil { + return nil, err + } + return f, nil } + func (client *Client) connect(password string) error { - if err := client.init(); err != nil { - return err - } - var f messages.StreamFeatures - if err := client.ReadElement(&f); err != nil { + if _, err := client.startStream(); err != nil { return err } if err := client.Out.Encode(&messages.TLSStartTLS{}); err != nil { @@ -123,106 +114,25 @@ func (client *Client) connect(password string) error { if err := tlsconn.VerifyHostname(client.JID.Domain); err != nil { return err } - if err := client.init(); err != nil { - return err - } - //auth: - if err := client.ReadElement(&f); err != nil { - return err - } - mechanism := "" - for _, m := range f.Mechanisms.Mechanism { - 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)) - fmt.Fprintf(client.conn, "%s\n", messages.NSSASL, enc) - break - } - if m == "DIGEST-MD5" { - mechanism = m - // Digest-MD5 authentication - fmt.Fprintf(client.conn, "\n", messages.NSSASL) - var ch string - if err := client.ReadElement(&ch); err != nil { - return err - } - b, err := base64.StdEncoding.DecodeString(string(ch)) - if err != nil { - return err - } - tokens := map[string]string{} - for _, token := range strings.Split(string(b), ",") { - kv := strings.SplitN(strings.TrimSpace(token), "=", 2) - if len(kv) == 2 { - if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { - kv[1] = kv[1][1 : len(kv[1])-1] - } - tokens[kv[0]] = kv[1] - } - } - realm, _ := tokens["realm"] - nonce, _ := tokens["nonce"] - qop, _ := tokens["qop"] - charset, _ := tokens["charset"] - cnonceStr := cnonce() - digestURI := "xmpp/" + client.JID.Domain - nonceCount := fmt.Sprintf("%08x", 1) - digest := saslDigestResponse(client.JID.Local, realm, password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount) - 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, "%s\n", messages.NSSASL, base64.StdEncoding.EncodeToString([]byte(message))) - - err = client.ReadElement(&ch) - if err != nil { - return err - } - _, err = base64.StdEncoding.DecodeString(ch) - if err != nil { - return err - } - fmt.Fprintf(client.conn, "\n", messages.NSSASL) - break - } - } - if mechanism == "" { - return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) - } - element, err := client.Read() + err := client.auth(password) if err != nil { return err } - if element.Name.Local != "success" { - return errors.New("auth failed: " + element.Name.Local) - } - - err = client.init() + _, err = client.startStream() if err != nil { return err } - if err := client.ReadElement(&f); err != nil { - return err - } // bind to resource - var msg string - if client.JID.Resource == "" { - msg = fmt.Sprintf("", messages.NSBind) - } else { - msg = fmt.Sprintf( - ` - %s - `, - messages.NSBind, client.JID.Resource) + bind := &messages.Bind{} + if client.JID.Resource != "" { + bind.Resource = client.JID.Resource } client.Out.Encode(&messages.IQClient{ Type: messages.IQTypeSet, - To: client.JID.Domain, - From: client.JID.Full(), + To: model.NewJID(client.JID.Domain), + From: client.JID, ID: utils.CreateCookieString(), - Body: []byte(msg), + Bind: bind, }) var iq messages.IQClient @@ -237,39 +147,10 @@ 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(string(iq.Body)) + return errors.New(fmt.Sprintf("%v", iq.Other)) } // set status - client.Out.Encode(&messages.PresenceClient{Show: "online", Status: "yaja client"}) + client.Send(&messages.PresenceClient{Show: "online", Status: "yaja client"}) - return nil -} - -func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string { - h := func(text string) []byte { - h := md5.New() - h.Write([]byte(text)) - return h.Sum(nil) - } - hex := func(bytes []byte) string { - return fmt.Sprintf("%x", bytes) - } - kd := func(secret, data string) []byte { - return h(secret + ":" + data) - } - - a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr - a2 := authenticate + ":" + digestURI - response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2)))) - return response -} - -func cnonce() string { - randSize := big.NewInt(0) - randSize.Lsh(big.NewInt(1), 64) - cn, err := rand.Int(rand.Reader, randSize) - if err != nil { - return "" - } - return fmt.Sprintf("%016x", cn) + return err } diff --git a/client/comm.go b/client/comm.go new file mode 100644 index 0000000..5fad5f8 --- /dev/null +++ b/client/comm.go @@ -0,0 +1,70 @@ +package client + +import ( + "encoding/xml" + "log" + + "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/server/utils" +) + +func (client *Client) Read() (*xml.StartElement, error) { + for { + nextToken, err := client.In.Token() + if err != nil { + return nil, err + } + switch nextToken.(type) { + case xml.StartElement: + element := nextToken.(xml.StartElement) + return &element, nil + } + } +} + +func (client *Client) ReadElement(p interface{}) error { + element, err := client.Read() + if err != nil { + return err + } + var iq *messages.IQClient + iq, ok := p.(*messages.IQClient) + if !ok { + iq = &messages.IQClient{} + } + err = client.In.DecodeElement(iq, element) + if err == nil && iq.Ping != nil { + log.Println("answer ping") + iq.Type = messages.IQTypeResult + iq.To = iq.From + iq.From = client.JID + client.Out.Encode(iq) + return nil + } + if ok { + return err + } + return client.In.DecodeElement(p, element) +} + +func (client *Client) Send(p interface{}) error { + msg, ok := p.(*messages.MessageClient) + if ok { + msg.From = client.JID + msg.ID = utils.CreateCookieString() + return client.Out.Encode(msg) + } + iq, ok := p.(*messages.IQClient) + if ok { + iq.From = client.JID + iq.ID = utils.CreateCookieString() + return client.Out.Encode(iq) + } + pc, ok := p.(*messages.PresenceClient) + if ok { + pc.From = client.JID + pc.ID = utils.CreateCookieString() + return client.Out.Encode(pc) + } + return client.Out.Encode(p) +} diff --git a/client/tls.go b/client/tls.go new file mode 100644 index 0000000..edd51a9 --- /dev/null +++ b/client/tls.go @@ -0,0 +1,11 @@ +package client + +import "crypto/tls" + +func (client *Client) TLSConnectionState() *tls.ConnectionState { + if tlsconn, ok := client.conn.(*tls.Conn); ok { + state := tlsconn.ConnectionState() + return &state + } + return nil +} diff --git a/daemon/config.go b/daemon/config.go index df9193f..15f7b3e 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -1,3 +1,10 @@ package daemon -var configPath string +import ( + "dev.sum7.eu/genofire/golang-lib/worker" +) + +var ( + configPath string + statesaveWorker *worker.Worker +) diff --git a/daemon/server.go b/daemon/server.go index 3c4c825..ed47aa0 100644 --- a/daemon/server.go +++ b/daemon/server.go @@ -25,7 +25,6 @@ import ( var ( serverConfig = &serverDaemon.Config{} db = &database.State{} - statesaveWorker *worker.Worker srv *server.Server certs *tls.Config extensionsClient extension.Extensions @@ -179,25 +178,27 @@ func reload() { } func init() { + extensionsClient = append(extensionsClient, &extension.Message{}, &extension.Presence{}, extension.IQExtensions{ - &extension.IQPrivate{}, + //&extension.IQPrivateBookmark{}, + //&extension.IQPrivateMetacontact{}, + //&extension.IQPrivateRoster{}, &extension.IQPing{}, - &extension.IQLast{}, - &extension.IQDisco{Database: db}, - &extension.IQRoster{Database: db}, - &extension.IQExtensionDiscovery{GetSpaces: func() []string { - return extensionsClient.Spaces() - }}, + //&extension.IQLast{}, + //&extension.IQDisco{Database: db}, + //&extension.IQRoster{Database: db}, + //&extension.IQExtensionDiscovery{GetSpaces: func() []string { + // return extensionsClient.Spaces() + //}}, }) extensionsServer = append(extensionsServer, extension.IQExtensions{ &extension.IQPing{}, }) - ServerCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-server.conf", "Path to configuration file") } diff --git a/daemon/tester.go b/daemon/tester.go index 72946a2..d0beea6 100644 --- a/daemon/tester.go +++ b/daemon/tester.go @@ -8,6 +8,7 @@ import ( "syscall" "time" + log "github.com/sirupsen/logrus" "golang.org/x/crypto/acme/autocert" "dev.sum7.eu/genofire/golang-lib/file" @@ -15,12 +16,15 @@ import ( "dev.sum7.eu/genofire/yaja/client" "dev.sum7.eu/genofire/yaja/daemon/tester" "dev.sum7.eu/genofire/yaja/messages" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) -var configTester = &tester.Config{} +var ( + configTester = &tester.Config{} + testerInstance = tester.NewTester() + testerWorker *worker.Worker +) // TesterCMD represents the serve command var TesterCMD = &cobra.Command{ @@ -35,15 +39,10 @@ var TesterCMD = &cobra.Command{ log.SetLevel(configTester.Logging) - if err := file.ReadJSON(configTester.StatePath, db); err != nil { + if err := file.ReadJSON(configTester.AccountsPath, testerInstance); err != nil { log.Warn("unable to load state file:", err) } - statesaveWorker = worker.NewWorker(time.Minute, func() { - file.SaveJSON(configTester.StatePath, db) - log.Info("save state to:", configTester.StatePath) - }) - // https server to handle acme (by letsencrypt) hs := &http.Server{ Addr: configTester.Webserver, @@ -71,19 +70,27 @@ var TesterCMD = &cobra.Command{ if err != nil { log.Fatal("unable to connect with main jabber client: ", err) } + defer mainClient.Close() for _, admin := range configTester.Admins { - mainClient.Out.Encode(&messages.MessageClient{ - From: mainClient.JID.Full(), - To: admin.Full(), + mainClient.Send(&messages.MessageClient{ + To: admin, Type: "chat", Body: "yaja tester starts", }) } - go statesaveWorker.Start() + testerInstance.Start(mainClient, configTester.Client.Password) + testerInstance.CheckStatus() + testerWorker = worker.NewWorker(time.Minute, func() { + testerInstance.CheckStatus() + file.SaveJSON(configTester.AccountsPath, testerInstance) + file.SaveJSON(configTester.OutputPath, testerInstance.Output()) + }) - log.Infoln("yaja tester started ") + go testerWorker.Start() + + log.Info("yaja tester started ") // Wait for INT/TERM sigs := make(chan os.Signal, 1) @@ -105,10 +112,11 @@ var TesterCMD = &cobra.Command{ } func quitTester() { + testerWorker.Close() + testerInstance.Close() srv.Close() - statesaveWorker.Close() - file.SaveJSON(configTester.StatePath, db) + file.SaveJSON(configTester.AccountsPath, db) } func init() { diff --git a/daemon/tester/bot.go b/daemon/tester/bot.go new file mode 100644 index 0000000..7229368 --- /dev/null +++ b/daemon/tester/bot.go @@ -0,0 +1,82 @@ +package tester + +import ( + "strings" + + log "github.com/sirupsen/logrus" + + "dev.sum7.eu/genofire/yaja/messages" +) + +func (t *Tester) StartBot(status *Status) { + for { + logCTX := log.WithField("jid", status.client.JID.Full()) + + element, err := status.client.Read() + if err != nil { + logCTX.Errorf("read client %s", err) + status.client.Close() + status.Login = false + return + } + + iq := &messages.IQClient{} + err = status.client.In.DecodeElement(iq, element) + if err == nil { + if iq.Ping != nil { + logCTX.Debug("answer ping") + iq.Type = messages.IQTypeResult + iq.To = iq.From + iq.From = status.client.JID + status.client.Out.Encode(iq) + } else { + logCTX.Warnf("unsupport iq recv: %v", iq) + } + continue + } + + pres := &messages.PresenceClient{} + err = status.client.In.DecodeElement(pres, element) + if err == nil { + if pres.Type == messages.PresenceTypeSubscribe { + pres.Type = messages.PresenceTypeSubscribed + pres.To = pres.From + status.client.Send(pres) + logCTX.WithField("from", pres.From.Full()).Info("accept new subscribe") + } else { + logCTX.Warnf("unsupported presence recv: %v", pres) + } + continue + } + + msg := &messages.MessageClient{} + err = status.client.In.DecodeElement(msg, element) + if err != nil { + logCTX.Warnf("unsupport xml recv: %s", err) + continue + } + logCTX = logCTX.WithField("from", msg.From.Full()).WithField("msg", msg.Body) + if msg.Error != nil { + logCTX.Debugf("recv msg with error %s: %s", msg.Error.Code, msg.Error.Text) + continue + + } + + msgText := strings.SplitN(msg.Body, " ", 2) + switch msgText[0] { + + case "ping": + status.client.Send(messages.MessageClient{Type: msg.Type, To: msg.From, Body: "pong"}) + + case "checkmsg": + if len(msgText) == 2 { + t.UpdateConnectionStatus(msg.From, status.client.JID, msgText[1]) + } else { + logCTX.Debug("undetect") + } + + default: + logCTX.Debug("undetect") + } + } +} diff --git a/daemon/tester/config.go b/daemon/tester/config.go index 235c0dc..23da6a3 100644 --- a/daemon/tester/config.go +++ b/daemon/tester/config.go @@ -6,13 +6,14 @@ import ( ) type Config struct { - TLSDir string `toml:"tlsdir"` - StatePath string `toml:"state_path"` - Logging log.Level `toml:"logging"` - Webserver string `toml:"webserver"` - Admins []model.JID `toml:"admins"` - Client struct { - JID model.JID `toml:"jid"` - Password string `toml:"password"` + TLSDir string `toml:"tlsdir"` + AccountsPath string `toml:"accounts_path"` + OutputPath string `toml:"output_path"` + Logging log.Level `toml:"logging"` + Webserver string `toml:"webserver"` + Admins []*model.JID `toml:"admins"` + Client struct { + JID *model.JID `toml:"jid"` + Password string `toml:"password"` } `toml:"client"` } diff --git a/daemon/tester/output.go b/daemon/tester/output.go new file mode 100644 index 0000000..be52a50 --- /dev/null +++ b/daemon/tester/output.go @@ -0,0 +1,70 @@ +package tester + +import ( + "fmt" + "strings" + + "dev.sum7.eu/genofire/yaja/model" + "github.com/FreifunkBremen/yanic/lib/jsontime" +) + +type Output struct { + Timestamp jsontime.Time `json:"timestamp"` + Status []*Status `json:"nodes"` + Links []*Link `json:"links"` +} + +type Link struct { + Source string `json:"source"` + Target string `json:"target"` + FromSource bool `json:"from_source"` + FromTarget bool `json:"from_target"` +} + +func (t *Tester) Output() *Output { + output := &Output{ + Timestamp: jsontime.Now(), + Status: make([]*Status, 0), + Links: make([]*Link, 0), + } + links := make(map[string]*Link) + + for from, status := range t.Status { + output.Status = append(output.Status, status) + + for to, linkOK := range status.Connections { + var key string + // keep source and target in the same order + switchSourceTarget := strings.Compare(from, to) > 0 + if switchSourceTarget { + key = fmt.Sprintf("%s-%s", from, to) + } else { + key = fmt.Sprintf("%s-%s", to, from) + } + if link := links[key]; link != nil { + if switchSourceTarget { + link.FromTarget = linkOK + } else { + link.FromSource = linkOK + } + continue + } + toJID := model.NewJID(to) + link := &Link{ + Source: status.JID.Domain, + Target: toJID.Domain, + FromSource: linkOK, + FromTarget: false, + } + if switchSourceTarget { + link.Source = toJID.Domain + link.Target = status.JID.Domain + link.FromSource = false + link.FromTarget = linkOK + } + links[key] = link + output.Links = append(output.Links, link) + } + } + return output +} diff --git a/daemon/tester/status.go b/daemon/tester/status.go new file mode 100644 index 0000000..1b05ac5 --- /dev/null +++ b/daemon/tester/status.go @@ -0,0 +1,52 @@ +package tester + +import ( + "crypto/tls" + + "dev.sum7.eu/genofire/yaja/client" + "dev.sum7.eu/genofire/yaja/model" +) + +type Status struct { + client *client.Client + password string + JID *model.JID `json:"jid"` + Domain string `json:"domain"` + Login bool `json:"is_online"` + MessageForConnection map[string]string `json:"-"` + Connections map[string]bool `json:"-"` + TLSVersion string `json:"tls_version"` +} + +func NewStatus(jid *model.JID, password string) *Status { + return &Status{ + JID: jid, + Domain: jid.Domain, + MessageForConnection: make(map[string]string), + Connections: make(map[string]bool), + } +} + +func (s *Status) Update() { + if s.client == nil || !s.Login { + s.Login = false + s.TLSVersion = "" + return + } + if tlsstate := s.client.TLSConnectionState(); tlsstate != nil { + switch tlsstate.Version { + case tls.VersionSSL30: + s.TLSVersion = "SSL 3.0" + case tls.VersionTLS10: + s.TLSVersion = "TLS 1.0" + case tls.VersionTLS11: + s.TLSVersion = "TLS 1.1" + case tls.VersionTLS12: + s.TLSVersion = "TLS 1.2" + default: + s.TLSVersion = "unknown " + string(tlsstate.Version) + } + } else { + s.TLSVersion = "" + } +} diff --git a/daemon/tester/tester.go b/daemon/tester/tester.go new file mode 100644 index 0000000..9b7643d --- /dev/null +++ b/daemon/tester/tester.go @@ -0,0 +1,152 @@ +package tester + +import ( + log "github.com/sirupsen/logrus" + + "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 { + mainClient *client.Client + Accounts map[string]string `json:"accounts"` + Status map[string]*Status `json:"-"` +} + +func NewTester() *Tester { + return &Tester{ + Accounts: make(map[string]string), + Status: make(map[string]*Status), + } +} + +func (t *Tester) Start(mainClient *client.Client, password string) { + + t.mainClient = mainClient + + status := NewStatus(mainClient.JID, password) + status.client = mainClient + status.Login = true + status.Update() + + t.Status[mainClient.JID.Bare()] = status + go t.StartBot(status) + + for jidString, passwd := range t.Accounts { + t.Connect(jidString, passwd) + } +} +func (t *Tester) Close() { + for _, s := range t.Status { + s.Login = false + s.client.Close() + } +} + +func (t *Tester) Connect(jidString, password string) { + logCTX := log.WithField("jid", jidString) + jid := model.NewJID(jidString) + status, ok := t.Status[jidString] + if !ok { + status = NewStatus(jid, password) + t.Status[jidString] = status + } else if status.JID == nil { + status.JID = jid + } + if status.Login { + logCTX.Warn("is already loggedin") + return + } + c, err := client.NewClient(jid, password) + if err != nil { + logCTX.Warnf("could not connect client: %s", err) + } else { + logCTX.Info("client connected") + status.Login = true + status.client = c + status.Update() + go t.StartBot(status) + } +} + +func (t *Tester) UpdateConnectionStatus(from, to *model.JID, recvmsg string) { + logCTX := log.WithFields(log.Fields{ + "jid": to.Full(), + "from": from.Full(), + "recvmsg": recvmsg, + }) + + status, ok := t.Status[from.Bare()] + if !ok { + logCTX.Warn("recv wrong msg") + return + } + msg, ok := status.MessageForConnection[to.Bare()] + logCTX = logCTX.WithField("msg", msg) + if !ok || msg != recvmsg { + logCTX.Warn("recv wrong msg") + return + } + delete(status.MessageForConnection, to.Bare()) + status.Connections[to.Bare()] = true + logCTX.Info("recv msg") + +} + +func (t *Tester) CheckStatus() { + send := 0 + online := 0 + connection := 0 + for ownJID, own := range t.Status { + logCTX := log.WithField("jid", ownJID) + if !own.Login { + pass, ok := t.Accounts[ownJID] + if ok { + t.Connect(ownJID, pass) + } + if !own.Login { + continue + } + } + online++ + for jid, s := range t.Status { + logCTXTo := logCTX.WithField("to", jid) + if jid == ownJID { + continue + } + connection++ + if own.MessageForConnection == nil { + own.MessageForConnection = make(map[string]string) + } + msg, ok := own.MessageForConnection[jid] + if ok { + logCTXTo = logCTXTo.WithField("old-msg", msg) + own.Connections[jid] = false + if ok, exists := own.Connections[jid]; !exists || ok { + logCTXTo.Warn("could not recv msg") + } else { + logCTXTo.Debug("could not recv msg") + } + } + msg = utils.CreateCookieString() + logCTXTo = logCTXTo.WithField("msg", msg) + + own.client.Send(&messages.MessageClient{ + Body: "checkmsg " + msg, + Type: messages.ChatTypeChat, + To: s.JID, + }) + own.MessageForConnection[jid] = msg + logCTXTo.Info("test send") + send++ + } + } + log.WithFields(log.Fields{ + "online": online, + "connection": connection, + "send": send, + "all": len(t.Status), + }).Info("checked complete") +} diff --git a/messages/connection.go b/messages/connection.go index 1702a34..ba3b7d7 100644 --- a/messages/connection.go +++ b/messages/connection.go @@ -15,12 +15,6 @@ type StreamFeatures struct { Session bool } -type StreamError struct { - XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` - Any xml.Name - Text string -} - // RFC 3920 C.3 TLS name space type TLSStartTLS struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` diff --git a/messages/error.go b/messages/error.go index c250282..5e37d84 100644 --- a/messages/error.go +++ b/messages/error.go @@ -2,11 +2,19 @@ package messages import "encoding/xml" +type StreamError struct { + XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` + Text string + + Any xml.Name `xml:",any"` +} + // ErrorClient element type ErrorClient struct { XMLName xml.Name `xml:"jabber:client error"` Code string `xml:"code,attr"` Type string `xml:"type,attr"` - Any xml.Name `xml:",any"` Text string `xml:"text"` + + Any xml.Name `xml:",any"` } diff --git a/messages/iq.go b/messages/iq.go index 4703731..d1167d0 100644 --- a/messages/iq.go +++ b/messages/iq.go @@ -1,6 +1,10 @@ package messages -import "encoding/xml" +import ( + "encoding/xml" + + "dev.sum7.eu/genofire/yaja/model" +) type IQType string @@ -13,13 +17,32 @@ const ( // IQ element - info/query type IQClient struct { - XMLName xml.Name `xml:"jabber:client iq"` - From string `xml:"from,attr"` - ID string `xml:"id,attr"` - To string `xml:"to,attr"` - Type IQType `xml:"type,attr"` - Error *ErrorClient `xml:"error"` - Bind Bind `xml:"bind"` - Body []byte `xml:",innerxml"` - // RosterRequest - better detection of iq's + XMLName xml.Name `xml:"jabber:client iq"` + From *model.JID `xml:"from,attr"` + ID string `xml:"id,attr"` + To *model.JID `xml:"to,attr"` + Type IQType `xml:"type,attr"` + Error *ErrorClient `xml:"error"` + Bind *Bind + Ping *Ping + PrivateQuery *IQPrivateQuery + PrivateRegister *IQPrivateRegister + // Any hasn't matched element + Other []XMLElement `xml:",any"` +} + +type Ping struct { + XMLName xml.Name `xml:"urn:xmpp:ping ping"` +} + +type IQPrivateQuery struct { + XMLName xml.Name `xml:"jabber:iq:private query"` + Body []byte `xml:",innerxml"` +} + +type IQPrivateRegister struct { + XMLName xml.Name `xml:"jabber:iq:register query"` + Instructions string `xml:"instructions"` + Username string `xml:"username"` + Password string `xml:"password"` } diff --git a/messages/presence.go b/messages/presence.go index 23d0cf7..9bc09da 100644 --- a/messages/presence.go +++ b/messages/presence.go @@ -1,6 +1,19 @@ package messages -import "encoding/xml" +import ( + "encoding/xml" + + "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 @@ -16,30 +29,46 @@ const ( // PresenceClient element type PresenceClient struct { - XMLName xml.Name `xml:"jabber:client presence"` - From string `xml:"from,attr,omitempty"` - ID string `xml:"id,attr,omitempty"` - To string `xml:"to,attr,omitempty"` - Type string `xml:"type,attr,omitempty"` - Lang string `xml:"lang,attr,omitempty"` + XMLName xml.Name `xml:"jabber:client presence"` + From *model.JID `xml:"from,attr,omitempty"` + ID string `xml:"id,attr,omitempty"` + To *model.JID `xml:"to,attr,omitempty"` + Type PresenceType `xml:"type,attr,omitempty"` + Lang string `xml:"lang,attr,omitempty"` Show string `xml:"show,omitempty"` // away, chat, dnd, xa Status string `xml:"status,omitempty"` // sb []clientText Priority string `xml:"priority,omitempty"` // Caps *ClientCaps `xml:"c"` - Error *ErrorClient `xml:"error"` - // Delay Delay `xml:"delay"` + Delay *Delay `xml:"delay"` + + 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 string `xml:"from,attr,omitempty"` - ID string `xml:"id,attr,omitempty"` - To string `xml:"to,attr,omitempty"` - Type string `xml:"type,attr,omitempty"` - Lang string `xml:"lang,attr,omitempty"` - Subject string `xml:"subject"` - Body string `xml:"body"` - Thread string `xml:"thread"` + 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 } diff --git a/messages/roster.go b/messages/roster.go new file mode 100644 index 0000000..ec240a1 --- /dev/null +++ b/messages/roster.go @@ -0,0 +1,19 @@ +package messages + +import ( + "encoding/xml" + + "dev.sum7.eu/genofire/yaja/model" +) + +type ClientQuery struct { + Item []RosterItem +} + +type RosterItem struct { + XMLName xml.Name `xml:"jabber:iq:roster item"` + JID *model.JID `xml:",attr"` + Name string `xml:",attr"` + Subscription string `xml:",attr"` + Group []string +} diff --git a/model/jid.go b/model/jid.go index 4a5f025..4a693a0 100644 --- a/model/jid.go +++ b/model/jid.go @@ -36,6 +36,9 @@ func NewJID(jidString string) *JID { // Bare get the "bare" jid func (jid *JID) Bare() string { + if jid == nil { + return "" + } if jid.Local != "" { return jid.Local + "@" + jid.Domain } @@ -46,6 +49,9 @@ func (jid *JID) String() string { return jid.Bare() } // Full get the "full" jid as string func (jid *JID) Full() string { + if jid == nil { + return "" + } if jid.Resource != "" { return jid.Bare() + "/" + jid.Resource } diff --git a/server/extension/iq.go b/server/extension/iq.go index d74b54e..eb9b4c8 100644 --- a/server/extension/iq.go +++ b/server/extension/iq.go @@ -51,7 +51,7 @@ func (iex IQExtensions) Process(element *xml.StartElement, client *utils.Client) // not extensions found if count != 1 { - log.Debug(msg.XMLName.Space, " - ", msg.Type, ": ", string(msg.Body)) + log.Debugf("%s - %s: %v", msg.XMLName.Space, msg.Type, msg.Other) } return true diff --git a/server/extension/iq_disco.go b/server/extension/iq_disco.bak similarity index 93% rename from server/extension/iq_disco.go rename to server/extension/iq_disco.bak index 45e0e05..f548d45 100644 --- a/server/extension/iq_disco.go +++ b/server/extension/iq_disco.bak @@ -5,6 +5,7 @@ import ( "dev.sum7.eu/genofire/yaja/database" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) @@ -59,8 +60,8 @@ func (ex *IQDisco) Get(msg *messages.IQClient, client *utils.Client) bool { // reply client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, Body: queryByte, } diff --git a/server/extension/iq_discovery.go b/server/extension/iq_discovery.bak similarity index 93% rename from server/extension/iq_discovery.go rename to server/extension/iq_discovery.bak index c2c0288..60ff323 100644 --- a/server/extension/iq_discovery.go +++ b/server/extension/iq_discovery.bak @@ -4,6 +4,7 @@ import ( "encoding/xml" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) @@ -59,8 +60,8 @@ func (ex *IQExtensionDiscovery) Get(msg *messages.IQClient, client *utils.Client // replay client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, Body: queryByte, } diff --git a/server/extension/iq_last.go b/server/extension/iq_last.bak similarity index 91% rename from server/extension/iq_last.go rename to server/extension/iq_last.bak index b06c5c8..9cddba8 100644 --- a/server/extension/iq_last.go +++ b/server/extension/iq_last.bak @@ -4,6 +4,7 @@ import ( "encoding/xml" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) @@ -47,8 +48,8 @@ func (ex *IQLast) Get(msg *messages.IQClient, client *utils.Client) bool { // reply client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, Body: queryByte, } diff --git a/server/extension/iq_ping.go b/server/extension/iq_ping.go index 0f65819..e589835 100644 --- a/server/extension/iq_ping.go +++ b/server/extension/iq_ping.go @@ -1,9 +1,8 @@ package extension import ( - "encoding/xml" - "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) @@ -16,20 +15,15 @@ func (ex *IQPing) Spaces() []string { return []string{"urn:xmpp:ping"} } func (ex *IQPing) Get(msg *messages.IQClient, client *utils.Client) bool { log := client.Log.WithField("extension", "ping").WithField("id", msg.ID) - // ping encode - type ping struct { - XMLName xml.Name `xml:"urn:xmpp:ping ping"` - } - pq := &ping{} - if err := xml.Unmarshal(msg.Body, pq); err != nil { + if msg.Ping == nil { return false } // reply client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, } diff --git a/server/extension/iq_private.go b/server/extension/iq_private.go deleted file mode 100644 index 52eb8c5..0000000 --- a/server/extension/iq_private.go +++ /dev/null @@ -1,52 +0,0 @@ -package extension - -import ( - "encoding/xml" - - "dev.sum7.eu/genofire/yaja/messages" - "dev.sum7.eu/genofire/yaja/server/utils" -) - -type IQPrivate struct { - IQExtension -} - -type iqPrivateQuery struct { - XMLName xml.Name `xml:"jabber:iq:private query"` - Body []byte `xml:",innerxml"` -} - -type iqPrivateExtension interface { - Handle(*messages.IQClient, *iqPrivateQuery, *utils.Client) bool -} - -func (ex *IQPrivate) Spaces() []string { return []string{"jabber:iq:private"} } - -func (ex *IQPrivate) Get(msg *messages.IQClient, client *utils.Client) bool { - log := client.Log.WithField("extension", "private").WithField("id", msg.ID) - - // query encode - q := &iqPrivateQuery{} - if err := xml.Unmarshal(msg.Body, q); err != nil { - return false - } - - // run every extensions - count := 0 - for _, e := range []iqPrivateExtension{ - &IQPrivateMetacontact{}, - &IQPrivateRoster{}, - &IQPrivateBookmark{}, - } { - if e.Handle(msg, q, client) { - count++ - } - } - - // not extensions found - if count != 1 { - log.Debug(msg.XMLName.Space, " - ", msg.Type, ": ", string(q.Body)) - } - - return true -} diff --git a/server/extension/iq_private_bookmarks.go b/server/extension/iq_private_bookmarks.bak similarity index 78% rename from server/extension/iq_private_bookmarks.go rename to server/extension/iq_private_bookmarks.bak index 2fd55e5..b5ac095 100644 --- a/server/extension/iq_private_bookmarks.go +++ b/server/extension/iq_private_bookmarks.bak @@ -4,14 +4,15 @@ import ( "encoding/xml" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) type IQPrivateBookmark struct { - iqPrivateExtension + IQExtension } -func (ex *IQPrivateBookmark) Handle(msg *messages.IQClient, q *iqPrivateQuery, client *utils.Client) bool { +func (ex *IQPrivateBookmark) Handle(msg *messages.IQClient, client *utils.Client) bool { log := client.Log.WithField("extension", "private").WithField("id", msg.ID) // storage encode @@ -37,8 +38,8 @@ func (ex *IQPrivateBookmark) Handle(msg *messages.IQClient, q *iqPrivateQuery, c // reply client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, Body: queryByte, } diff --git a/server/extension/iq_private_metacontacts.go b/server/extension/iq_private_metacontacts.bak similarity index 79% rename from server/extension/iq_private_metacontacts.go rename to server/extension/iq_private_metacontacts.bak index 5819cd9..b8db2ff 100644 --- a/server/extension/iq_private_metacontacts.go +++ b/server/extension/iq_private_metacontacts.bak @@ -4,14 +4,15 @@ import ( "encoding/xml" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) type IQPrivateMetacontact struct { - iqPrivateExtension + IQExtension } -func (ex *IQPrivateMetacontact) Handle(msg *messages.IQClient, q *iqPrivateQuery, client *utils.Client) bool { +func (ex *IQPrivateMetacontact) Handle(msg *messages.IQClient, client *utils.Client) bool { log := client.Log.WithField("extension", "private-metacontact").WithField("id", msg.ID) // storage encode @@ -38,8 +39,8 @@ func (ex *IQPrivateMetacontact) Handle(msg *messages.IQClient, q *iqPrivateQuery // reply client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, Body: queryByte, } diff --git a/server/extension/iq_private_roster.go b/server/extension/iq_private_roster.bak similarity index 81% rename from server/extension/iq_private_roster.go rename to server/extension/iq_private_roster.bak index 61c67d3..2e13ca5 100644 --- a/server/extension/iq_private_roster.go +++ b/server/extension/iq_private_roster.bak @@ -4,14 +4,15 @@ import ( "encoding/xml" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/utils" ) type IQPrivateRoster struct { - iqPrivateExtension + IQExtension } -func (ex *IQPrivateRoster) Handle(msg *messages.IQClient, q *iqPrivateQuery, client *utils.Client) bool { +func (ex *IQPrivateRoster) Handle(msg *messages.IQClient, client *utils.Client) bool { log := client.Log.WithField("extension", "private").WithField("id", msg.ID) // roster encode @@ -42,8 +43,8 @@ func (ex *IQPrivateRoster) Handle(msg *messages.IQClient, q *iqPrivateQuery, cli // reply client.Messages <- &messages.IQClient{ Type: messages.IQTypeResult, - To: client.JID.String(), - From: client.JID.Domain, + To: client.JID, + From: model.NewJID(client.JID.Domain), ID: msg.ID, Body: queryByte, } diff --git a/server/extension/iq_roster.go b/server/extension/iq_roster.bak similarity index 100% rename from server/extension/iq_roster.go rename to server/extension/iq_roster.bak diff --git a/server/toclient/connect.go b/server/toclient/connect.go index eecf3d7..fed6e1f 100644 --- a/server/toclient/connect.go +++ b/server/toclient/connect.go @@ -3,12 +3,12 @@ package toclient import ( "crypto/tls" "encoding/base64" - "encoding/xml" "fmt" "strings" "dev.sum7.eu/genofire/yaja/database" "dev.sum7.eu/genofire/yaja/messages" + "dev.sum7.eu/genofire/yaja/model" "dev.sum7.eu/genofire/yaja/server/extension" "dev.sum7.eu/genofire/yaja/server/state" "dev.sum7.eu/genofire/yaja/server/utils" @@ -213,28 +213,22 @@ func (state *AuthedStream) Process() state.State { return nil } - var q messages.Bind - err = xml.Unmarshal(msg.Body, q) - if err != nil { + if msg.Bind == nil { state.Client.Log.Warn("is no iq bind: ", err) return nil } - if q.Resource == "" { + if msg.Bind.Resource == "" { state.Client.JID.Resource = makeResource() } else { - state.Client.JID.Resource = q.Resource + state.Client.JID.Resource = msg.Bind.Resource } state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full()) state.Client.Out.Encode(&messages.IQClient{ Type: messages.IQTypeResult, - To: state.Client.JID.String(), - From: state.Client.JID.Domain, + To: state.Client.JID, + From: model.NewJID(state.Client.JID.Domain), ID: msg.ID, - Body: []byte(fmt.Sprintf( - ` - %s - `, - messages.NSBind, state.Client.JID.Full())), + Bind: &messages.Bind{JID: state.Client.JID}, }) return state.Next diff --git a/server/toclient/register.go b/server/toclient/register.go index 54ca034..9cdbdab 100644 --- a/server/toclient/register.go +++ b/server/toclient/register.go @@ -2,7 +2,6 @@ package toclient import ( "encoding/xml" - "fmt" "dev.sum7.eu/genofire/yaja/database" "dev.sum7.eu/genofire/yaja/messages" @@ -42,27 +41,21 @@ func (state *RegisterFormRequest) Process() state.State { state.Client.Log.Warn("iq with error: ", msg.Error.Code) return state } - type query struct { - XMLName xml.Name `xml:"query"` - } - q := &query{} - err := xml.Unmarshal(msg.Body, q) - if q.XMLName.Space != messages.NSIQRegister || err != nil { - state.Client.Log.Warn("is no iq register: ", err) + if msg.PrivateRegister == nil { + state.Client.Log.Warn("is no iq register") return nil } state.Client.Out.Encode(&messages.IQClient{ Type: messages.IQTypeResult, - To: state.Client.JID.String(), - From: state.Client.JID.Domain, + To: state.Client.JID, + From: model.NewJID(state.Client.JID.Domain), ID: msg.ID, - Body: []byte(fmt.Sprintf(` - Choose a username and password for use with this service. - - - - `, messages.NSIQRegister)), + PrivateRegister: &messages.IQPrivateRegister{ + Instructions: "Choose a username and password for use with this service.", + Username: "", + Password: "", + }, }) return state.Next } @@ -103,32 +96,22 @@ func (state *RegisterRequest) Process() state.State { state.Client.Log.Warn("iq with error: ", msg.Error.Code) return state } - type query struct { - XMLName xml.Name `xml:"query"` - Username string `xml:"username"` - Password string `xml:"password"` - } - q := &query{} - err = xml.Unmarshal(msg.Body, q) - if err != nil { + if msg.PrivateRegister == nil { state.Client.Log.Warn("is no iq register: ", err) return nil } - state.Client.JID.Local = q.Username + state.Client.JID.Local = msg.PrivateRegister.Username state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full()) - account := model.NewAccount(state.Client.JID, q.Password) + account := model.NewAccount(state.Client.JID, msg.PrivateRegister.Password) err = state.database.AddAccount(account) if err != nil { state.Client.Out.Encode(&messages.IQClient{ - Type: messages.IQTypeResult, - To: state.Client.JID.String(), - From: state.Client.JID.Domain, - ID: msg.ID, - Body: []byte(fmt.Sprintf(` - %s - %s - `, messages.NSIQRegister, q.Username, q.Password)), + Type: messages.IQTypeResult, + To: state.Client.JID, + From: model.NewJID(state.Client.JID.Domain), + ID: msg.ID, + PrivateRegister: msg.PrivateRegister, Error: &messages.ErrorClient{ Code: "409", Type: "cancel", @@ -143,8 +126,8 @@ func (state *RegisterRequest) Process() state.State { } state.Client.Out.Encode(&messages.IQClient{ Type: messages.IQTypeResult, - To: state.Client.JID.String(), - From: state.Client.JID.Domain, + To: state.Client.JID, + From: model.NewJID(state.Client.JID.Domain), ID: msg.ID, })