sum7
/
yaja
Archived
1
0
Fork 0

write some dirty extension (to be compatible with gajim

This commit is contained in:
Martin Geno 2017-12-17 13:31:02 +01:00
parent 1aceea7133
commit 5d8c92a76a
No known key found for this signature in database
GPG Key ID: F0D39A37E925E941
20 changed files with 599 additions and 84 deletions

View File

@ -30,7 +30,7 @@ var (
statesaveWorker *worker.Worker statesaveWorker *worker.Worker
srv *server.Server srv *server.Server
certs *tls.Config certs *tls.Config
extensions []extension.Extension extensions extension.Extensions
) )
// serverCmd represents the serve command // serverCmd represents the serve command
@ -178,8 +178,19 @@ func reload() {
} }
func init() { func init() {
extensions = append(extensions,
&extension.Message{},
extension.IQExtensions{
&extension.Private{},
&extension.Ping{},
&extension.Disco{Database: db},
&extension.Roster{Database: db},
&extension.ExtensionDiscovery{GetSpaces: func() []string {
return extensions.Spaces()
}},
})
RootCmd.AddCommand(serverCmd) RootCmd.AddCommand(serverCmd)
serverCmd.Flags().StringVarP(&configPath, "config", "c", "yaja.conf", "Path to configuration file") serverCmd.Flags().StringVarP(&configPath, "config", "c", "yaja.conf", "Path to configuration file")
extensions = append(extensions, &extension.Message{}, &extension.Roster{Database: db})
} }

View File

@ -66,3 +66,18 @@ func (s *State) Authenticate(jid *model.JID, password string) (bool, error) {
} }
return false, nil return false, nil
} }
func (s *State) GetAccount(jid *model.JID) *model.Account {
logger := log.WithField("database", "get")
if domain, ok := s.Domains[jid.Domain]; ok {
if acc, ok := domain.Accounts[jid.Local]; ok {
return acc
} else {
logger.Debug("account not found")
}
} else {
logger.Debug("domain not found")
}
return nil
}

View File

@ -16,4 +16,6 @@ const (
NSIQRegister = "jabber:iq:register" NSIQRegister = "jabber:iq:register"
NSFeaturesIQRegister = "http://jabber.org/features/iq-register" NSFeaturesIQRegister = "http://jabber.org/features/iq-register"
NSDisco = "http://jabber.org/protocol/disco#info"
) )

View File

@ -29,10 +29,11 @@ func (d *Domain) UpdateAccount(a *Account) error {
} }
type Account struct { type Account struct {
Local string `json:"-"` Local string `json:"-"`
Domain *Domain `json:"-"` Domain *Domain `json:"-"`
Password string `json:"password"` Password string `json:"password"`
Roster map[string]*Buddy `json:"roster"` Roster map[string]*Buddy `json:"roster"`
Bookmarks map[string]*Bookmark `json:"bookmarks"`
} }
func NewAccount(jid *JID, password string) *Account { func NewAccount(jid *JID, password string) *Account {

View File

@ -15,3 +15,6 @@ type Buddy struct {
Subscription int `json:"subscription"` Subscription int `json:"subscription"`
Ask int `json:"ask"` Ask int `json:"ask"`
} }
type Bookmark struct {
}

58
server/extension/iq.go Normal file
View File

@ -0,0 +1,58 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type IQExtensions []IQExtension
type IQExtension interface {
Extension
Get(*messages.IQ, *utils.Client) bool
Set(*messages.IQ, *utils.Client) bool
}
func (iex IQExtensions) Spaces() (result []string) {
for _, extension := range iex {
spaces := extension.Spaces()
result = append(result, spaces...)
}
return result
}
func (iex IQExtensions) Process(element *xml.StartElement, client *utils.Client) bool {
log := client.Log.WithField("extension", "iq")
// iq encode
var msg messages.IQ
if err := client.In.DecodeElement(&msg, element); err != nil {
return false
}
log = log.WithField("id", msg.ID)
// run every extensions
count := 0
for _, extension := range iex {
switch msg.Type {
case messages.IQTypeGet:
if extension.Get(&msg, client) {
count++
}
case messages.IQTypeSet:
if extension.Set(&msg, client) {
count++
}
}
}
// not extensions found
if count != 1 {
log.Debug(msg.XMLName.Space, " - ", msg.Type, ": ", string(msg.Body))
}
return true
}

View File

@ -0,0 +1,72 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/database"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type Disco struct {
IQExtension
Database *database.State
}
func (r *Disco) Spaces() []string { return []string{} }
func (r *Disco) Get(msg *messages.IQ, client *utils.Client) bool {
log := client.Log.WithField("extension", "disco-item").WithField("id", msg.ID)
// query encode
type query struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
Body []byte `xml:",innerxml"`
}
q := &query{}
err := xml.Unmarshal(msg.Body, q)
if err != nil {
return false
}
// answer query
q.Body = []byte{}
// build answer body
type item struct {
XMLName xml.Name `xml:"item"`
JID string `xml:"jid,attr"`
}
if acc := r.Database.GetAccount(client.JID); acc != nil {
for jid, _ := range acc.Bookmarks {
itemByte, err := xml.Marshal(&item{
JID: jid,
})
if err != nil {
log.Warn(err)
continue
}
q.Body = append(q.Body, itemByte...)
}
}
// decode query
queryByte, err := xml.Marshal(q)
if err != nil {
log.Warn(err)
return false
}
// reply
client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
Body: queryByte,
})
log.Debug("send")
return true
}

View File

@ -0,0 +1,72 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type ExtensionDiscovery struct {
IQExtension
GetSpaces func() []string
}
func (ex *ExtensionDiscovery) Spaces() []string { return []string{} }
func (ex *ExtensionDiscovery) Get(msg *messages.IQ, client *utils.Client) bool {
log := client.Log.WithField("extension", "roster").WithField("id", msg.ID)
// query encode
type query struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
Body []byte `xml:",innerxml"`
}
q := &query{}
err := xml.Unmarshal(msg.Body, q)
if err != nil {
return false
}
// answer query
q.Body = []byte{}
// build answer body
type feature struct {
XMLName xml.Name `xml:"feature"`
Var string `xml:"var,attr"`
}
for _, namespace := range ex.GetSpaces() {
if namespace == "" {
continue
}
itemByte, err := xml.Marshal(&feature{
Var: namespace,
})
if err != nil {
log.Warn(err)
continue
}
q.Body = append(q.Body, itemByte...)
}
// decode query
queryByte, err := xml.Marshal(q)
if err != nil {
log.Warn(err)
return false
}
// replay
client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
Body: queryByte,
})
log.Debug("send")
return true
}

View File

@ -0,0 +1,40 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type Ping struct {
IQExtension
}
func (p *Ping) Spaces() []string { return []string{"urn:xmpp:ping"} }
func (p *Ping) Get(msg *messages.IQ, 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{}
err := xml.Unmarshal(msg.Body, pq)
if err != nil {
return false
}
// reply
client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
})
log.Debug("send")
return true
}

View File

@ -0,0 +1,52 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type Private struct {
IQExtension
}
type privateQuery struct {
XMLName xml.Name `xml:"jabber:iq:private query"`
Body []byte `xml:",innerxml"`
}
type ioPrivateExtension interface {
Handle(*messages.IQ, *privateQuery, *utils.Client) bool
}
func (p *Private) Spaces() []string { return []string{"jabber:iq:private"} }
func (p *Private) Get(msg *messages.IQ, client *utils.Client) bool {
log := client.Log.WithField("extension", "private").WithField("id", msg.ID)
// query encode
q := &privateQuery{}
err := xml.Unmarshal(msg.Body, q)
if err != nil {
return false
}
// run every extensions
count := 0
for _, e := range []ioPrivateExtension{
&PrivateMetacontact{},
&PrivateRoster{},
} {
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
}

View File

@ -0,0 +1,51 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type PrivateMetacontact struct {
ioPrivateExtension
}
func (p *PrivateMetacontact) Handle(msg *messages.IQ, q *privateQuery, client *utils.Client) bool {
log := client.Log.WithField("extension", "private-metacontact").WithField("id", msg.ID)
// storage encode
type storage struct {
XMLName xml.Name `xml:"storage:metacontacts storage"`
}
s := &storage{}
err := xml.Unmarshal(q.Body, s)
if err != nil {
return false
}
/*
TODO full implement XEP-0209
https://xmpp.org/extensions/xep-0209.html
*/
queryByte, err := xml.Marshal(&privateQuery{
Body: q.Body,
})
if err != nil {
log.Warn(err)
return true
}
// reply
client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
Body: queryByte,
})
log.Debug("send")
return true
}

View File

@ -0,0 +1,55 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type PrivateRoster struct {
ioPrivateExtension
}
func (p *PrivateRoster) Handle(msg *messages.IQ, q *privateQuery, client *utils.Client) bool {
log := client.Log.WithField("extension", "private").WithField("id", msg.ID)
// roster encode
type roster struct {
XMLName xml.Name `xml:"roster:delimiter roster"`
Body []byte `xml:",innerxml"`
}
r := &roster{}
err := xml.Unmarshal(q.Body, r)
if err != nil {
return false
}
rosterByte, err := xml.Marshal(&roster{
Body: []byte("::"),
})
if err != nil {
log.Warn(err)
return true
}
queryByte, err := xml.Marshal(&privateQuery{
Body: rosterByte,
})
if err != nil {
log.Warn(err)
return true
}
// reply
client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
Body: queryByte,
})
log.Debug("send")
return true
}

View File

@ -0,0 +1,74 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/database"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type Roster struct {
IQExtension
Database *database.State
}
func (r *Roster) Spaces() []string { return []string{"jabber:iq:roster"} }
func (r *Roster) Get(msg *messages.IQ, client *utils.Client) bool {
log := client.Log.WithField("extension", "roster").WithField("id", msg.ID)
// query encode
type query struct {
XMLName xml.Name `xml:"jabber:iq:roster query"`
Version string `xml:"ver,attr"`
Body []byte `xml:",innerxml"`
}
q := &query{}
err := xml.Unmarshal(msg.Body, q)
if err != nil {
return false
}
// answer query
q.Body = []byte{}
q.Version = "1"
// build answer body
type item struct {
XMLName xml.Name `xml:"item"`
JID string `xml:"jid,attr"`
}
if acc := r.Database.GetAccount(client.JID); acc != nil {
for jid, _ := range acc.Roster {
itemByte, err := xml.Marshal(&item{
JID: jid,
})
if err != nil {
log.Warn(err)
continue
}
q.Body = append(q.Body, itemByte...)
}
}
// decode query
queryByte, err := xml.Marshal(q)
if err != nil {
log.Warn(err)
return false
}
// reply
client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
Body: queryByte,
})
log.Debug("send")
return true
}

View File

@ -6,6 +6,33 @@ import (
"github.com/genofire/yaja/server/utils" "github.com/genofire/yaja/server/utils"
) )
type Extensions []Extension
type Extension interface { type Extension interface {
Process(*xml.StartElement, *utils.Client) bool Process(*xml.StartElement, *utils.Client) bool
Spaces() []string
}
func (ex Extensions) Spaces() (result []string) {
for _, extension := range ex {
result = append(result, extension.Spaces()...)
}
return result
}
func (ex Extensions) Process(element *xml.StartElement, client *utils.Client) {
log := client.Log.WithField("extension", "all")
// run every extensions
count := 0
for _, extension := range ex {
if extension.Process(element, client) {
count++
}
}
// not extensions found
if count != 1 {
log.Debug(element)
}
} }

View File

@ -10,6 +10,8 @@ type Message struct {
Extension Extension
} }
func (m *Message) Spaces() []string { return []string{} }
func (m *Message) Process(element *xml.StartElement, client *utils.Client) bool { func (m *Message) Process(element *xml.StartElement, client *utils.Client) bool {
return false return false
} }

View File

@ -1,27 +0,0 @@
package extension
import (
"encoding/xml"
"github.com/genofire/yaja/database"
"github.com/genofire/yaja/messages"
"github.com/genofire/yaja/server/utils"
)
type Roster struct {
Extension
Database *database.State
}
func (r *Roster) Process(element *xml.StartElement, client *utils.Client) bool {
var msg messages.IQ
if err := client.In.DecodeElement(&msg, element); err != nil {
client.Log.Warn("is no iq: ", err)
return false
}
if msg.Type != messages.IQTypeGet {
client.Log.Warn("is no get iq")
return false
}
return true
}

View File

@ -22,7 +22,7 @@ type Server struct {
LoggingClient log.Level LoggingClient log.Level
RegisterEnable bool RegisterEnable bool
RegisterDomains []string RegisterDomains []string
Extensions []extension.Extension Extensions extension.Extensions
} }
func (srv *Server) Start() { func (srv *Server) Start() {
@ -74,7 +74,7 @@ func (srv *Server) handleServer(conn net.Conn) {
func (srv *Server) handleClient(conn net.Conn) { func (srv *Server) handleClient(conn net.Conn) {
log.Info("new client connection:", conn.RemoteAddr()) log.Info("new client connection:", conn.RemoteAddr())
client := utils.NewClient(conn, srv.LoggingClient) client := utils.NewClient(conn, srv.LoggingClient)
state := toclient.ConnectionStartup(srv.Database, srv.TLSConfig, srv.TLSManager, srv.DomainRegisterAllowed) state := toclient.ConnectionStartup(srv.Database, srv.TLSConfig, srv.TLSManager, srv.DomainRegisterAllowed, srv.Extensions)
for { for {
state, client = state.Process(client) state, client = state.Process(client)

View File

@ -16,8 +16,8 @@ import (
) )
// ConnectionStartup return steps through TCP TLS state // ConnectionStartup return steps through TCP TLS state
func ConnectionStartup(db *database.State, tlsconfig *tls.Config, tlsmgmt *autocert.Manager, registerAllowed utils.DomainRegisterAllowed) state.State { func ConnectionStartup(db *database.State, tlsconfig *tls.Config, tlsmgmt *autocert.Manager, registerAllowed utils.DomainRegisterAllowed, extensions []extension.Extension) state.State {
receiving := &ReceivingClient{} receiving := &ReceivingClient{Extensions: extensions}
sending := &SendingClient{Next: receiving} sending := &SendingClient{Next: receiving}
authedstream := &AuthedStream{Next: sending} authedstream := &AuthedStream{Next: sending}
authedstart := &AuthedStart{Next: authedstream} authedstart := &AuthedStart{Next: authedstream}
@ -213,6 +213,8 @@ func (state *AuthedStream) Process(client *utils.Client) (state.State, *utils.Cl
client.Log = client.Log.WithField("jid", client.JID.Full()) client.Log = client.Log.WithField("jid", client.JID.Full())
client.Out.Encode(&messages.IQ{ client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult, Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID, ID: msg.ID,
Body: []byte(fmt.Sprintf( Body: []byte(fmt.Sprintf(
`<bind xmlns='%s'> `<bind xmlns='%s'>
@ -223,50 +225,3 @@ func (state *AuthedStream) Process(client *utils.Client) (state.State, *utils.Cl
return state.Next, client return state.Next, client
} }
// SendingClient state
type SendingClient struct {
Next state.State
}
// Process messages
func (state *SendingClient) Process(client *utils.Client) (state.State, *utils.Client) {
client.Log = client.Log.WithField("state", "normal")
client.Log.Debug("sending")
// sending
go func() {
select {
case msg := <-client.Messages:
err := client.Out.Encode(msg)
client.Log.Info(err)
case <-client.OnClose():
return
}
}()
client.Log.Debug("receiving")
return state.Next, client
}
// ReceivingClient state
type ReceivingClient struct {
Extensions []extension.Extension
}
// Process messages
func (state *ReceivingClient) Process(client *utils.Client) (state.State, *utils.Client) {
element, err := client.Read()
if err != nil {
client.Log.Warn("unable to read: ", err)
return nil, client
}
count := 0
for _, extension := range state.Extensions {
if extension.Process(element, client) {
count++
}
}
if count != 1 {
client.Log.WithField("extension", count).Debug(element)
}
return state, client
}

46
server/toclient/normal.go Normal file
View File

@ -0,0 +1,46 @@
package toclient
import (
"github.com/genofire/yaja/server/extension"
"github.com/genofire/yaja/server/state"
"github.com/genofire/yaja/server/utils"
)
// SendingClient state
type SendingClient struct {
Next state.State
}
// Process messages
func (state *SendingClient) Process(client *utils.Client) (state.State, *utils.Client) {
client.Log = client.Log.WithField("state", "normal")
client.Log.Debug("sending")
// sending
go func() {
select {
case msg := <-client.Messages:
err := client.Out.Encode(msg)
client.Log.Info(err)
case <-client.OnClose():
return
}
}()
client.Log.Debug("receiving")
return state.Next, client
}
// ReceivingClient state
type ReceivingClient struct {
Extensions extension.Extensions
}
// Process messages
func (state *ReceivingClient) Process(client *utils.Client) (state.State, *utils.Client) {
element, err := client.Read()
if err != nil {
client.Log.Warn("unable to read: ", err)
return nil, client
}
state.Extensions.Process(element, client)
return state, client
}

View File

@ -53,6 +53,8 @@ func (state *RegisterFormRequest) Process(client *utils.Client) (state.State, *u
} }
client.Out.Encode(&messages.IQ{ client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult, Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID, ID: msg.ID,
Body: []byte(fmt.Sprintf(`<query xmlns='%s'><instructions> Body: []byte(fmt.Sprintf(`<query xmlns='%s'><instructions>
Choose a username and password for use with this service. Choose a username and password for use with this service.
@ -118,6 +120,8 @@ func (state *RegisterRequest) Process(client *utils.Client) (state.State, *utils
if err != nil { if err != nil {
client.Out.Encode(&messages.IQ{ client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult, Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID, ID: msg.ID,
Body: []byte(fmt.Sprintf(`<query xmlns='%s'> Body: []byte(fmt.Sprintf(`<query xmlns='%s'>
<username>%s</username> <username>%s</username>
@ -137,6 +141,8 @@ func (state *RegisterRequest) Process(client *utils.Client) (state.State, *utils
} }
client.Out.Encode(&messages.IQ{ client.Out.Encode(&messages.IQ{
Type: messages.IQTypeResult, Type: messages.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID, ID: msg.ID,
}) })