sum7
/
yaja
Archived
1
0
Fork 0

replace server with bot library

This commit is contained in:
Martin/Geno 2018-12-14 23:46:58 +01:00
parent 0ad62245e8
commit 7f4f6d5f71
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
45 changed files with 283 additions and 1810 deletions

11
bot/handler.go Normal file
View File

@ -0,0 +1,11 @@
package bot
import (
"dev.sum7.eu/genofire/yaja/xmpp"
)
type Handler interface {
Presence(*Bot, *xmpp.PresenceClient) bool
Message(*Bot, *xmpp.MessageClient) bool
IQ(*Bot, *xmpp.IQClient) bool
}

36
bot/handler_commandmsg.go Normal file
View File

@ -0,0 +1,36 @@
package bot
import (
"strings"
"dev.sum7.eu/genofire/yaja/xmpp"
)
type CommandMessageHander struct {
Handler
Commands map[string]func(*Bot, *xmpp.MessageClient, string)
}
func (h *CommandMessageHander) Presence(bot *Bot, pres *xmpp.PresenceClient) bool {
return false
}
func (h *CommandMessageHander) Message(bot *Bot, msg *xmpp.MessageClient) bool {
msgText := strings.SplitN(msg.Body, " ", 2)
if msgText != nil {
cmd := msgText[0]
if f, ok := h.Commands[cmd]; ok {
args := ""
if len(msgText) >= 2 {
args = msgText[1]
}
f(bot, msg, args)
return true
}
}
return false
}
func (h *CommandMessageHander) IQ(bot *Bot, iq *xmpp.IQClient) bool {
return false
}

51
bot/handler_subscribe.go Normal file
View File

@ -0,0 +1,51 @@
package bot
import (
"dev.sum7.eu/genofire/yaja/xmpp"
)
type SubscribeHander struct {
Handler
Disabled *bool
}
func (h *SubscribeHander) Presence(bot *Bot, pres *xmpp.PresenceClient) bool {
/*
if *(h.Disabled) {
return false
}
*/
switch pres.Type {
case xmpp.PresenceTypeSubscribe:
pres.Type = xmpp.PresenceTypeSubscribed
pres.To = pres.From
pres.From = nil
//accept new subscribe
bot.client.Send(pres)
pres.Type = xmpp.PresenceTypeSubscribe
pres.ID = ""
// request also subscribe
bot.client.Send(pres)
return true
case xmpp.PresenceTypeSubscribed:
return true
case xmpp.PresenceTypeUnsubscribe:
return true
case xmpp.PresenceTypeUnsubscribed:
return true
case xmpp.PresenceTypeUnavailable:
// ignore Unavailable messages
return true
default:
return false
}
}
func (h *SubscribeHander) Message(bot *Bot, msg *xmpp.MessageClient) bool {
return false
}
func (h *SubscribeHander) IQ(bot *Bot, iq *xmpp.IQClient) bool {
return false
}

89
bot/main.go Normal file
View File

@ -0,0 +1,89 @@
package bot
import (
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/yaja/client"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/xmpp/base"
)
type Bot struct {
AutoSubscribe bool
Logging *log.Entry
Handlers []Handler
client *client.Client
jid *xmppbase.JID
password string
}
func NewBot(jid *xmppbase.JID, password string) *Bot {
bot := Bot{
jid: jid,
password: password,
Logging: log.WithField("jid", jid.String()),
}
bot.Handlers = append(bot.Handlers, &SubscribeHander{Disabled: &bot.AutoSubscribe})
return &bot
}
func (bot *Bot) Start() {
if bot.client == nil {
bot.client = &client.Client{
JID: bot.jid,
Logging: bot.Logging,
}
if bot.client.Connect(bot.password) != nil {
bot.Logging.Fatal("was not able to connect")
}
go func() {
if err := bot.client.Start(); err != nil {
bot.Logging.Fatal("was not able to reconnect")
}
}()
}
bot.run()
}
func (bot *Bot) run() {
for {
element, more := bot.client.Recv()
if !more {
bot.Logging.Info("could not recv msg, closed")
return
}
handled := false
switch element.(type) {
case *xmpp.PresenceClient:
pres := element.(*xmpp.PresenceClient)
for _, f := range bot.Handlers {
if f.Presence(bot, pres) {
handled = true
break
}
}
case *xmpp.MessageClient:
msg := element.(*xmpp.MessageClient)
for _, f := range bot.Handlers {
if f.Message(bot, msg) {
handled = true
break
}
}
case *xmpp.IQClient:
iq := element.(*xmpp.IQClient)
for _, f := range bot.Handlers {
if f.IQ(bot, iq) {
handled = true
break
}
}
}
if handled {
bot.Logging.Debugf("recv handled: %v", element)
} else {
bot.Logging.Infof("recv unhandled: %v", element)
}
}
}

11
bot/utils.go Normal file
View File

@ -0,0 +1,11 @@
package bot
import "dev.sum7.eu/genofire/yaja/xmpp"
func (bot *Bot) Answer(msg *xmpp.MessageClient, body string) {
bot.client.Send(&xmpp.MessageClient{
To: msg.From,
Type: msg.Type,
Body: body,
})
}

76
cmd/bot.go Normal file
View File

@ -0,0 +1,76 @@
package cmd
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"dev.sum7.eu/genofire/golang-lib/file"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
xmppbot "dev.sum7.eu/genofire/yaja/bot"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/xmpp/base"
)
type botConfig struct {
LogLevel log.Level `toml:"log_level"`
JID *xmppbase.JID `toml:"jid"`
Password string `toml:"password"`
}
// BotCMD represents the serve command
var BotCMD = &cobra.Command{
Use: "bot",
Short: "runs xmpp ping bot",
Example: "yaja bot -c /etc/yaja.conf",
Run: func(cmd *cobra.Command, args []string) {
config := &botConfig{}
if err := file.ReadTOML(configPath, config); err != nil {
log.Fatal("unable to load config file:", err)
}
log.SetLevel(config.LogLevel)
log.Infof("yaja bot starting with jid: %s", config.JID.String())
pingBog := xmppbot.NewBot(config.JID, config.Password)
pingBog.AutoSubscribe = true
botCounter := 0
pingBog.Handlers = append(pingBog.Handlers, &xmppbot.CommandMessageHander{Commands: map[string]func(*xmppbot.Bot, *xmpp.MessageClient, string){
// reaction on command ping
"ping": func(bot *xmppbot.Bot, msg *xmpp.MessageClient, args string) {
bot.Answer(msg, "pong")
},
// reaction on command time
"time": func(bot *xmppbot.Bot, msg *xmpp.MessageClient, args string) {
bot.Answer(msg, time.Now().String())
},
"inc": func(bot *xmppbot.Bot, msg *xmpp.MessageClient, args string) {
botCounter += 1
bot.Answer(msg, fmt.Sprintf("counter: %d", botCounter))
},
}})
go pingBog.Start()
log.Infoln("yaja bot started")
// Wait for INT/TERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("received", sig)
},
}
func init() {
BotCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-bot.conf", "path to configuration file")
RootCMD.AddCommand(BotCMD)
}

View File

@ -1,4 +1,4 @@
package daemon package cmd
import ( import (
"dev.sum7.eu/genofire/golang-lib/worker" "dev.sum7.eu/genofire/golang-lib/worker"

View File

@ -1,18 +0,0 @@
package cmd
import (
"dev.sum7.eu/genofire/yaja/daemon"
"github.com/spf13/cobra"
)
// DaemonCMD represents the serve command
var DaemonCMD = &cobra.Command{
Use: "daemon",
Short: "daemon of yaja",
}
func init() {
DaemonCMD.AddCommand(daemon.ServerCMD)
DaemonCMD.AddCommand(daemon.TesterCMD)
RootCMD.AddCommand(DaemonCMD)
}

View File

@ -1,4 +1,4 @@
package daemon package cmd
import ( import (
"os" "os"
@ -10,7 +10,7 @@ import (
"dev.sum7.eu/genofire/golang-lib/file" "dev.sum7.eu/genofire/golang-lib/file"
"dev.sum7.eu/genofire/golang-lib/worker" "dev.sum7.eu/genofire/golang-lib/worker"
"dev.sum7.eu/genofire/yaja/client" "dev.sum7.eu/genofire/yaja/client"
"dev.sum7.eu/genofire/yaja/daemon/tester" "dev.sum7.eu/genofire/yaja/cmd/tester"
"dev.sum7.eu/genofire/yaja/xmpp" "dev.sum7.eu/genofire/yaja/xmpp"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -98,12 +98,11 @@ var TesterCMD = &cobra.Command{
func quitTester() { func quitTester() {
testerWorker.Close() testerWorker.Close()
testerInstance.Close() testerInstance.Close()
srv.Close()
file.SaveJSON(configTester.AccountsPath, db) //file.SaveJSON(configTester.AccountsPath, db)
} }
func init() { func init() {
TesterCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-tester.conf", "path to configuration file") TesterCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-tester.conf", "path to configuration file")
RootCMD.AddCommand(TesterCMD)
} }

1
cmd/tester_test.go Normal file
View File

@ -0,0 +1 @@
package cmd

View File

@ -1,204 +0,0 @@
package daemon
import (
"crypto/tls"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"golang.org/x/crypto/acme/autocert"
serverDaemon "dev.sum7.eu/genofire/yaja/daemon/server"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/server/extension"
"dev.sum7.eu/genofire/golang-lib/file"
"dev.sum7.eu/genofire/golang-lib/worker"
"dev.sum7.eu/genofire/yaja/server"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
serverConfig = &serverDaemon.Config{}
db = &database.State{}
srv *server.Server
certs *tls.Config
extensionsClient extension.Extensions
extensionsServer extension.Extensions
)
// ServerCMD represents the serve command
var ServerCMD = &cobra.Command{
Use: "server",
Short: "runs xmpp server",
Example: "yaja daemon server -c /etc/yaja.conf",
Run: func(cmd *cobra.Command, args []string) {
if err := file.ReadTOML(configPath, serverConfig); err != nil {
log.Fatal("unable to load config file:", err)
}
log.SetLevel(serverConfig.Logging.Level)
if err := file.ReadJSON(serverConfig.StatePath, db); err != nil {
log.Warn("unable to load state file:", err)
}
statesaveWorker = worker.NewWorker(time.Minute, func() {
file.SaveJSON(serverConfig.StatePath, db)
log.Info("save state to:", serverConfig.StatePath)
})
m := autocert.Manager{
Cache: autocert.DirCache(serverConfig.TLSDir),
Prompt: autocert.AcceptTOS,
}
// https server to handle acme (by letsencrypt)
for _, addr := range serverConfig.Address.Webserver {
hs := &http.Server{
Addr: addr,
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
}
go func(hs *http.Server, addr string) {
if err := hs.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
log.Errorf("webserver with addr %s: %s", addr, err)
}
}(hs, addr)
}
srv = &server.Server{
TLSManager: &m,
Database: db,
ClientAddr: serverConfig.Address.Client,
ServerAddr: serverConfig.Address.Server,
LoggingClient: serverConfig.Logging.LevelClient,
LoggingServer: serverConfig.Logging.LevelServer,
RegisterEnable: serverConfig.Register.Enable,
RegisterDomains: serverConfig.Register.Domains,
ExtensionsServer: extensionsServer,
ExtensionsClient: extensionsClient,
}
go statesaveWorker.Start()
go srv.Start()
log.Infoln("yaja started ")
// Wait for INT/TERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1)
for sig := range sigs {
log.Infoln("received", sig)
switch sig {
case syscall.SIGTERM:
log.Panic("terminated")
os.Exit(0)
case syscall.SIGQUIT:
quit()
case syscall.SIGHUP:
quit()
case syscall.SIGUSR1:
reload()
}
}
},
}
func quit() {
srv.Close()
statesaveWorker.Close()
file.SaveJSON(serverConfig.StatePath, db)
}
func reload() {
log.Info("start reloading...")
var configNewData *serverDaemon.Config
if err := file.ReadTOML(configPath, configNewData); err != nil {
log.Warn("unable to load config file:", err)
return
}
log.SetLevel(configNewData.Logging.Level)
srv.LoggingClient = configNewData.Logging.LevelClient
srv.LoggingServer = configNewData.Logging.LevelServer
srv.RegisterEnable = configNewData.Register.Enable
srv.RegisterDomains = configNewData.Register.Domains
//TODO fetch changing address (to set restart)
if configNewData.StatePath != serverConfig.StatePath {
statesaveWorker.Close()
statesaveWorker := worker.NewWorker(time.Minute, func() {
file.SaveJSON(configNewData.StatePath, db)
log.Info("save state to:", configNewData.StatePath)
})
go statesaveWorker.Start()
}
restartServer := false
if configNewData.TLSDir != serverConfig.TLSDir {
m := autocert.Manager{
Cache: autocert.DirCache(serverConfig.TLSDir),
Prompt: autocert.AcceptTOS,
}
certs = &tls.Config{GetCertificate: m.GetCertificate}
restartServer = true
}
if restartServer {
newServer := &server.Server{
TLSConfig: certs,
Database: db,
ClientAddr: configNewData.Address.Client,
ServerAddr: configNewData.Address.Server,
LoggingClient: configNewData.Logging.LevelClient,
RegisterEnable: configNewData.Register.Enable,
RegisterDomains: configNewData.Register.Domains,
ExtensionsServer: extensionsServer,
ExtensionsClient: extensionsClient,
}
log.Warn("reloading need a restart:")
go newServer.Start()
//TODO should fetch new server error
srv.Close()
srv = newServer
}
serverConfig = configNewData
log.Info("reloaded")
}
func init() {
extensionsClient = append(extensionsClient,
&extension.Message{},
&extension.Presence{},
extension.IQExtensions{
//&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()
//}},
})
extensionsServer = append(extensionsServer,
extension.IQExtensions{
&extension.IQPing{},
})
ServerCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-server.conf", "path to configuration file")
}

View File

@ -1,24 +0,0 @@
package server
import (
log "github.com/sirupsen/logrus"
)
type Config struct {
TLSDir string `toml:"tlsdir"`
StatePath string `toml:"state_path"`
Logging struct {
Level log.Level `toml:"level"`
LevelClient log.Level `toml:"level_client"`
LevelServer log.Level `toml:"level_server"`
} `toml:"logging"`
Register struct {
Enable bool `toml:"enable"`
Domains []string `toml:"domains"`
} `toml:"register"`
Address struct {
Webserver []string `toml:"webserver"`
Client []string `toml:"client"`
Server []string `toml:"server"`
} `toml:"address"`
}

View File

@ -1 +0,0 @@
adsa

View File

@ -1 +0,0 @@
package daemon

View File

@ -1,58 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
)
type IQExtensions []IQExtension
type IQExtension interface {
Extension
Get(*xmpp.IQClient, *utils.Client) bool
Set(*xmpp.IQClient, *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 xmpp.IQClient
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 xmpp.IQTypeGet:
if extension.Get(&msg, client) {
count++
}
case xmpp.IQTypeSet:
if extension.Set(&msg, client) {
count++
}
}
}
// not extensions found
if count != 1 {
log.Debugf("%s - %s: %s", msg.XMLName.Space, msg.Type, xmpp.XMLChildrenString(msg.Other))
}
return true
}

View File

@ -1,72 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type IQDisco struct {
IQExtension
Database *database.State
}
func (ex *IQDisco) Spaces() []string { return []string{"http://jabber.org/protocol/disco#items"} }
func (ex *IQDisco) Get(msg *xmpp.IQClient, 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{}
if err := xml.Unmarshal(msg.Body, q); 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 := ex.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.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmpp.NewJID(client.JID.Domain),
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,72 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type IQExtensionDiscovery struct {
IQExtension
GetSpaces func() []string
}
func (ex *IQExtensionDiscovery) Spaces() []string { return []string{} }
func (ex *IQExtensionDiscovery) Get(msg *xmpp.IQClient, 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{}
if err := xml.Unmarshal(msg.Body, q); 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.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmpp.NewJID(client.JID.Domain),
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,60 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
//TODO Draft
type IQLast struct {
IQExtension
}
func (ex *IQLast) Spaces() []string { return []string{"jabber:iq:last"} }
func (ex *IQLast) Get(msg *xmpp.IQClient, client *utils.Client) bool {
log := client.Log.WithField("extension", "last").WithField("id", msg.ID)
// query encode
type query struct {
XMLName xml.Name `xml:"jabber:iq:last query"`
Seconds uint `xml:"seconds,attr,omitempty"`
Body []byte `xml:",innerxml"`
}
q := &query{}
if err := xml.Unmarshal(msg.Body, q); 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"`
}
// decode query
queryByte, err := xml.Marshal(q)
if err != nil {
log.Warn(err)
return false
}
// reply
client.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmpp.NewJID(client.JID.Domain),
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,33 +0,0 @@
package extension
import (
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/xmpp/base"
)
type IQPing struct {
IQExtension
}
func (ex *IQPing) Spaces() []string { return []string{"urn:xmpp:ping"} }
func (ex *IQPing) Get(msg *xmpp.IQClient, client *utils.Client) bool {
log := client.Log.WithField("extension", "ping").WithField("id", msg.ID)
if msg.Ping == nil {
return false
}
// reply
client.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmppbase.NewJID(client.JID.Domain),
ID: msg.ID,
}
log.Debug("send")
return true
}

View File

@ -1,50 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type IQPrivateBookmark struct {
IQExtension
}
func (ex *IQPrivateBookmark) Handle(msg *xmpp.IQClient, client *utils.Client) bool {
log := client.Log.WithField("extension", "private").WithField("id", msg.ID)
// storage encode
type storage struct {
XMLName xml.Name `xml:"storage:bookmarks storage"`
}
s := &storage{}
if err := xml.Unmarshal(q.Body, s); err != nil {
return false
}
/*
TODO full implement
*/
queryByte, err := xml.Marshal(&iqPrivateQuery{
Body: q.Body,
})
if err != nil {
log.Warn(err)
return true
}
// reply
client.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmpp.NewJID(client.JID.Domain),
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,51 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type IQPrivateMetacontact struct {
IQExtension
}
func (ex *IQPrivateMetacontact) Handle(msg *xmpp.IQClient, 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{}
if err := xml.Unmarshal(q.Body, s); err != nil {
return false
}
/*
TODO full implement XEP-0209
https://xmpp.org/extensions/xep-0209.html
*/
queryByte, err := xml.Marshal(&iqPrivateQuery{
Body: q.Body,
})
if err != nil {
log.Warn(err)
return true
}
// reply
client.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmpp.NewJID(client.JID.Domain),
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,55 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type IQPrivateRoster struct {
IQExtension
}
func (ex *IQPrivateRoster) Handle(msg *xmpp.IQClient, 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{}
if err := xml.Unmarshal(q.Body, r); err != nil {
return false
}
rosterByte, err := xml.Marshal(&roster{
Body: []byte("::"),
})
if err != nil {
log.Warn(err)
return true
}
queryByte, err := xml.Marshal(&iqPrivateQuery{
Body: rosterByte,
})
if err != nil {
log.Warn(err)
return true
}
// reply
client.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID,
From: xmpp.NewJID(client.JID.Domain),
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,73 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type IQRoster struct {
IQExtension
Database *database.State
}
func (ex *IQRoster) Spaces() []string { return []string{"jabber:iq:roster"} }
func (ex *IQRoster) Get(msg *xmpp.IQClient, 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{}
if err := xml.Unmarshal(msg.Body, q); 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 := ex.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.Messages <- &xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: client.JID.String(),
From: client.JID.Domain,
ID: msg.ID,
Body: queryByte,
}
log.Debug("send")
return true
}

View File

@ -1,38 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type Extensions []Extension
type Extension interface {
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

@ -1,19 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/server/utils"
)
type Message struct {
Extension
}
//TODO Draft
func (m *Message) Spaces() []string { return []string{} }
func (m *Message) Process(element *xml.StartElement, client *utils.Client) bool {
return false
}

View File

@ -1,32 +0,0 @@
package extension
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
)
type Presence struct {
Extension
}
//TODO Draft
func (p *Presence) Spaces() []string { return []string{} }
func (p *Presence) Process(element *xml.StartElement, client *utils.Client) bool {
log := client.Log.WithField("extension", "presence")
// iq encode
var msg xmpp.PresenceClient
if err := client.In.DecodeElement(&msg, element); err != nil {
return false
}
client.Messages <- &xmpp.PresenceClient{
ID: msg.ID,
}
log.Debug("send")
return true
}

View File

@ -1,128 +0,0 @@
package server
import (
"crypto/tls"
"net"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/acme/autocert"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/server/extension"
"dev.sum7.eu/genofire/yaja/server/toclient"
"dev.sum7.eu/genofire/yaja/server/toserver"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp/base"
)
type Server struct {
TLSConfig *tls.Config
TLSManager *autocert.Manager
ClientAddr []string
ServerAddr []string
Database *database.State
LoggingClient log.Level
LoggingServer log.Level
RegisterEnable bool
RegisterDomains []string
ExtensionsClient extension.Extensions
ExtensionsServer extension.Extensions
}
func (srv *Server) Start() {
for _, addr := range srv.ServerAddr {
socket, err := net.Listen("tcp", addr)
if err != nil {
log.Warn("create server socket: ", err.Error())
break
}
go srv.listenServer(socket)
}
for _, addr := range srv.ClientAddr {
socket, err := net.Listen("tcp", addr)
if err != nil {
log.Warn("create client socket: ", err.Error())
break
}
go srv.listenClient(socket)
}
}
func (srv *Server) listenServer(s2s net.Listener) {
for {
conn, err := s2s.Accept()
if err != nil {
log.Warn("accepting server connection: ", err.Error())
break
}
go srv.handleServer(conn)
}
}
func (srv *Server) listenClient(c2s net.Listener) {
for {
conn, err := c2s.Accept()
if err != nil {
log.Warn("accepting client connection: ", err.Error())
break
}
go srv.handleClient(conn)
}
}
func (srv *Server) handleServer(conn net.Conn) {
log.Info("new server connection:", conn.RemoteAddr())
client := utils.NewClient(conn, srv.LoggingClient)
client.Log = client.Log.WithField("c", "s2s")
state := toserver.ConnectionStartup(srv.Database, srv.TLSConfig, srv.TLSManager, srv.ExtensionsServer, client)
for {
state = state.Process()
if state == nil {
client.Log.Info("disconnect")
client.Close()
return
}
// run next state
}
}
func (srv *Server) handleClient(conn net.Conn) {
log.Info("new client connection:", conn.RemoteAddr())
client := utils.NewClient(conn, srv.LoggingServer)
client.Log = client.Log.WithField("c", "c2s")
state := toclient.ConnectionStartup(srv.Database, srv.TLSConfig, srv.TLSManager, srv.DomainRegisterAllowed, srv.ExtensionsClient, client)
for {
state = state.Process()
if state == nil {
client.Log.Info("disconnect")
client.Close()
//s.DisconnectBus <- Disconnect{Jid: client.jid}
return
}
// run next state
}
}
func (srv *Server) DomainRegisterAllowed(jid *xmppbase.JID) bool {
if jid.Domain == "" {
return false
}
for _, domain := range srv.RegisterDomains {
if domain == jid.Domain {
return !srv.RegisterEnable
}
}
return srv.RegisterEnable
}
func (srv *Server) Close() {
}

View File

@ -1,117 +0,0 @@
package state
import (
"crypto/tls"
"fmt"
"golang.org/x/crypto/acme/autocert"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/xmpp/base"
)
// Start state
type Start struct {
Next State
Client *utils.Client
}
// Process message
func (state *Start) Process() State {
state.Client.Log = state.Client.Log.WithField("state", "stream")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
if element.Name.Space != xmpp.NSStream || element.Name.Local != "stream" {
state.Client.Log.Warn("is no stream")
return state
}
for _, attr := range element.Attr {
if attr.Name.Local == "to" {
state.Client.JID = &xmppbase.JID{Domain: attr.Value}
state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full())
}
}
if state.Client.JID == nil {
state.Client.Log.Warn("no 'to' domain readed")
return nil
}
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
<stream:stream id='%x' version='1.0' xmlns='%s' xmlns:stream='%s'>`,
xmpp.CreateCookie(), xmpp.NSClient, xmpp.NSStream)
fmt.Fprintf(state.Client.Conn, `<stream:features>
<starttls xmlns='%s'>
<required/>
</starttls>
</stream:features>`,
xmpp.NSStream)
return state.Next
}
// TLSUpgrade state
type TLSUpgrade struct {
Next State
Client *utils.Client
TLSConfig *tls.Config
TLSManager *autocert.Manager
}
// Process message
func (state *TLSUpgrade) Process() State {
state.Client.Log = state.Client.Log.WithField("state", "tls upgrade")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
if element.Name.Space != xmpp.NSStartTLS || element.Name.Local != "starttls" {
state.Client.Log.Warn("is no starttls", element)
return nil
}
fmt.Fprintf(state.Client.Conn, "<proceed xmlns='%s'/>", xmpp.NSStartTLS)
// perform the TLS handshake
var tlsConfig *tls.Config
if m := state.TLSManager; m != nil {
var cert *tls.Certificate
cert, err = m.GetCertificate(&tls.ClientHelloInfo{ServerName: state.Client.JID.Domain})
if err != nil {
state.Client.Log.Warn("no cert in tls manger found: ", err)
return nil
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{*cert},
}
}
if tlsConfig == nil {
tlsConfig = state.TLSConfig
if tlsConfig != nil {
tlsConfig.ServerName = state.Client.JID.Domain
} else {
state.Client.Log.Warn("no tls config found: ", err)
return nil
}
}
tlsConn := tls.Server(state.Client.Conn, tlsConfig)
err = tlsConn.Handshake()
if err != nil {
state.Client.Log.Warn("unable to tls handshake: ", err)
return nil
}
// restart the Connection
state.Client.SetConnecting(tlsConn)
return state.Next
}

View File

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

View File

@ -1,30 +0,0 @@
package state
import "dev.sum7.eu/genofire/yaja/server/utils"
// State processes the stream and moves to the next state
type State interface {
Process() State
}
// Start state
type Debug struct {
Next State
Client *utils.Client
}
// Process message
func (state *Debug) Process() State {
state.Client.Log = state.Client.Log.WithField("state", "debug")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
state.Client.Log.Info(element)
return state.Next
}

View File

@ -1,237 +0,0 @@
package toclient
import (
"crypto/tls"
"encoding/base64"
"fmt"
"strings"
"golang.org/x/crypto/acme/autocert"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/server/extension"
"dev.sum7.eu/genofire/yaja/server/state"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/xmpp/base"
"dev.sum7.eu/genofire/yaja/xmpp/iq"
)
// ConnectionStartup return steps through TCP TLS state
func ConnectionStartup(db *database.State, tlsconfig *tls.Config, tlsmgmt *autocert.Manager, registerAllowed utils.DomainRegisterAllowed, extensions extension.Extensions, c *utils.Client) state.State {
receiving := &state.ReceivingClient{Extensions: extensions, Client: c}
sending := &state.SendingClient{Next: receiving, Client: c}
authedstream := &AuthedStream{Next: sending, Client: c}
authedstart := &AuthedStart{Next: authedstream, Client: c}
tlsauth := &SASLAuth{
Next: authedstart,
Client: c,
database: db,
domainRegisterAllowed: registerAllowed,
}
tlsstream := &TLSStream{
Next: tlsauth,
Client: c,
domainRegisterAllowed: registerAllowed,
}
tlsupgrade := &state.TLSUpgrade{
Next: tlsstream,
Client: c,
TLSConfig: tlsconfig,
TLSManager: tlsmgmt,
}
return &state.Start{Next: tlsupgrade, Client: c}
}
// TLSStream state
type TLSStream struct {
Next state.State
Client *utils.Client
domainRegisterAllowed utils.DomainRegisterAllowed
}
// Process xmpp
func (state *TLSStream) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "tls stream")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
if element.Name.Space != xmpp.NSStream || element.Name.Local != "stream" {
state.Client.Log.Warn("is no stream")
return state
}
if state.domainRegisterAllowed(state.Client.JID) {
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
<stream:stream id='%x' version='1.0' xmlns='%s' xmlns:stream='%s'>
<stream:features>
<register xmlns='%s'/>
<mechanisms xmlns='%s'>
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>`,
xmpp.CreateCookie(), xmpp.NSClient, xmpp.NSStream,
xmpp.NSSASL, xmppiq.NSFeatureRegister)
} else {
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
<stream:stream id='%x' version='1.0' xmlns='%s' xmlns:stream='%s'>
<stream:features>
<mechanisms xmlns='%s'>
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>`,
xmpp.CreateCookie(), xmpp.NSClient, xmpp.NSStream,
xmpp.NSSASL)
}
return state.Next
}
// SASLAuth state
type SASLAuth struct {
Next state.State
Client *utils.Client
database *database.State
domainRegisterAllowed utils.DomainRegisterAllowed
}
// Process xmpp
func (state *SASLAuth) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "sasl auth")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
// read the full auth stanza
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
var auth xmpp.SASLAuth
if err = state.Client.In.DecodeElement(&auth, element); err != nil {
state.Client.Log.Info("start substate for registration")
return &RegisterFormRequest{
Next: &RegisterRequest{
Next: state.Next,
Client: state.Client,
database: state.database,
domainRegisterAllowed: state.domainRegisterAllowed,
},
Client: state.Client,
element: element,
domainRegisterAllowed: state.domainRegisterAllowed,
}
}
data, err := base64.StdEncoding.DecodeString(auth.Body)
if err != nil {
state.Client.Log.Warn("body decode: ", err)
return nil
}
info := strings.Split(string(data), "\x00")
// should check that info[1] starts with state.Client.JID
state.Client.JID.Local = info[1]
state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full())
success, err := state.database.Authenticate(state.Client.JID, info[2])
if err != nil {
state.Client.Log.Warn("auth: ", err)
return nil
}
if success {
state.Client.Log.Info("success auth")
fmt.Fprintf(state.Client.Conn, "<success xmlns='%s'/>", xmpp.NSSASL)
return state.Next
}
state.Client.Log.Warn("failed auth")
fmt.Fprintf(state.Client.Conn, "<failure xmlns='%s'><not-authorized/></failure>", xmpp.NSSASL)
return nil
}
// AuthedStart state
type AuthedStart struct {
Next state.State
Client *utils.Client
}
// Process xmpp
func (state *AuthedStart) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "authed started")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
_, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
<stream:stream xmlns:stream='%s' xml:lang='en' from='%s' id='%x' version='1.0' xmlns='%s'>
<stream:features>
<bind xmlns='%s'>
<required/>
</bind>
</stream:features>`,
xmpp.NSStream, state.Client.JID.Domain, xmpp.CreateCookie(), xmpp.NSClient,
xmpp.NSBind)
return state.Next
}
// AuthedStream state
type AuthedStream struct {
Next state.State
Client *utils.Client
}
// Process xmpp
func (state *AuthedStream) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "authed stream")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
// check that it's a bind request
// read bind request
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
var msg xmpp.IQClient
if err = state.Client.In.DecodeElement(&msg, element); err != nil {
state.Client.Log.Warn("is no iq: ", err)
return nil
}
if msg.Type != xmpp.IQTypeSet {
state.Client.Log.Warn("is no set iq")
return nil
}
if msg.Error != nil {
state.Client.Log.Warn("iq with error: ", msg.Error.Code)
return nil
}
if msg.Bind == nil {
state.Client.Log.Warn("is no iq bind: ", err)
return nil
}
if msg.Bind.Resource == "" {
state.Client.JID.Resource = makeResource()
} else {
state.Client.JID.Resource = msg.Bind.Resource
}
state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full())
state.Client.Out.Encode(&xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: state.Client.JID,
From: xmppbase.NewJID(state.Client.JID.Domain),
ID: msg.ID,
Bind: &xmpp.Bind{JID: state.Client.JID},
})
return state.Next
}

View File

@ -1,137 +0,0 @@
package toclient
import (
"encoding/xml"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/server/state"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
"dev.sum7.eu/genofire/yaja/xmpp/base"
"dev.sum7.eu/genofire/yaja/xmpp/iq"
)
type RegisterFormRequest struct {
Next state.State
Client *utils.Client
domainRegisterAllowed utils.DomainRegisterAllowed
element *xml.StartElement
}
// Process message
func (state *RegisterFormRequest) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "register form request")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
if !state.domainRegisterAllowed(state.Client.JID) {
state.Client.Log.Error("unpossible to reach this state, register on this domain is not allowed")
return nil
}
var msg xmpp.IQClient
if err := state.Client.In.DecodeElement(&msg, state.element); err != nil {
state.Client.Log.Warn("is no iq: ", err)
return state
}
if msg.Type != xmpp.IQTypeGet {
state.Client.Log.Warn("is no get iq")
return state
}
if msg.Error != nil {
state.Client.Log.Warn("iq with error: ", msg.Error.Code)
return state
}
if msg.Register == nil {
state.Client.Log.Warn("is no iq register")
return nil
}
state.Client.Out.Encode(&xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: state.Client.JID,
From: xmppbase.NewJID(state.Client.JID.Domain),
ID: msg.ID,
Register: &xmppiq.Register{
Instructions: "Choose a username and password for use with this service.",
Username: "",
Password: "",
},
})
return state.Next
}
type RegisterRequest struct {
Next state.State
Client *utils.Client
database *database.State
domainRegisterAllowed utils.DomainRegisterAllowed
}
// Process message
func (state *RegisterRequest) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "register request")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
if !state.domainRegisterAllowed(state.Client.JID) {
state.Client.Log.Error("unpossible to reach this state, register on this domain is not allowed")
return nil
}
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
var msg xmpp.IQClient
if err = state.Client.In.DecodeElement(&msg, element); err != nil {
state.Client.Log.Warn("is no iq: ", err)
return state
}
if msg.Type != xmpp.IQTypeGet {
state.Client.Log.Warn("is no get iq")
return state
}
if msg.Error != nil {
state.Client.Log.Warn("iq with error: ", msg.Error.Code)
return state
}
if msg.Register == nil {
state.Client.Log.Warn("is no iq register: ", err)
return nil
}
state.Client.JID.Local = msg.Register.Username
state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full())
account := model.NewAccount(state.Client.JID, msg.Register.Password)
err = state.database.AddAccount(account)
if err != nil {
state.Client.Out.Encode(&xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: state.Client.JID,
From: xmppbase.NewJID(state.Client.JID.Domain),
ID: msg.ID,
Register: msg.Register,
Error: &xmpp.ErrorClient{
Code: "409",
Type: "cancel",
StanzaErrorGroup: xmpp.StanzaErrorGroup{
Conflict: &xml.Name{},
},
},
})
state.Client.Log.Warn("database error: ", err)
return state
}
state.Client.Out.Encode(&xmpp.IQClient{
Type: xmpp.IQTypeResult,
To: state.Client.JID,
From: xmppbase.NewJID(state.Client.JID.Domain),
ID: msg.ID,
})
state.Client.Log.Infof("registered client %s", state.Client.JID.Bare())
return state.Next
}

View File

@ -1,14 +0,0 @@
package toclient
import (
"crypto/rand"
"fmt"
)
func makeResource() string {
var buf [16]byte
if _, err := rand.Reader.Read(buf[:]); err != nil {
panic("Failed to read random bytes: " + err.Error())
}
return fmt.Sprintf("%x", buf)
}

View File

@ -1,142 +0,0 @@
package toserver
import (
"crypto/tls"
"encoding/base64"
"encoding/xml"
"fmt"
"golang.org/x/crypto/acme/autocert"
"dev.sum7.eu/genofire/yaja/database"
"dev.sum7.eu/genofire/yaja/server/extension"
"dev.sum7.eu/genofire/yaja/server/state"
"dev.sum7.eu/genofire/yaja/server/utils"
"dev.sum7.eu/genofire/yaja/xmpp"
)
// ConnectionStartup return steps through TCP TLS state
func ConnectionStartup(db *database.State, tlsconfig *tls.Config, tlsmgmt *autocert.Manager, extensions extension.Extensions, c *utils.Client) state.State {
receiving := &state.ReceivingClient{Extensions: extensions, Client: c}
sending := &state.SendingClient{Next: receiving, Client: c}
tlsstream := &TLSStream{
Next: sending,
Client: c,
}
tlsupgrade := &state.TLSUpgrade{
Next: tlsstream,
Client: c,
TLSConfig: tlsconfig,
TLSManager: tlsmgmt,
}
dail := &Dailback{
Next: tlsupgrade,
Client: c,
}
return &state.Start{Next: dail, Client: c}
}
// TLSStream state
type Dailback struct {
Next state.State
Client *utils.Client
}
// Process xmpp
func (state *Dailback) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "dialback")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
// dailback encode
type dailback struct {
XMLName xml.Name `xml:"urn:xmpp:ping ping"`
}
db := &dailback{}
if err = state.Client.In.DecodeElement(db, element); err != nil {
return state.Next
}
state.Client.Log.Info(db)
return state.Next
}
// TLSStream state
type TLSStream struct {
Next state.State
Client *utils.Client
domainRegisterAllowed utils.DomainRegisterAllowed
}
// Process xmpp
func (state *TLSStream) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "tls stream")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
if element.Name.Space != xmpp.NSStream || element.Name.Local != "stream" {
state.Client.Log.Warn("is no stream")
return state
}
fmt.Fprintf(state.Client.Conn, `<?xml version='1.0'?>
<stream:stream id='%x' version='1.0' xmlns='%s' xmlns:stream='%s'>
<stream:features>
<mechanisms xmlns='%s'>
<mechanism>EXTERNAL</mechanism>
</mechanisms>
<bidi xmlns='urn:xmpp:features:bidi'/>
</stream:features>`,
xmpp.CreateCookie(), xmpp.NSClient, xmpp.NSStream,
xmpp.NSSASL)
return state.Next
}
// SASLAuth state
type SASLAuth struct {
Next state.State
Client *utils.Client
database *database.State
domainRegisterAllowed utils.DomainRegisterAllowed
}
// Process xmpp
func (state *SASLAuth) Process() state.State {
state.Client.Log = state.Client.Log.WithField("state", "sasl auth")
state.Client.Log.Debug("running")
defer state.Client.Log.Debug("leave")
// read the full auth stanza
element, err := state.Client.Read()
if err != nil {
state.Client.Log.Warn("unable to read: ", err)
return nil
}
var auth xmpp.SASLAuth
if err = state.Client.In.DecodeElement(&auth, element); err != nil {
return nil
}
data, err := base64.StdEncoding.DecodeString(auth.Body)
if err != nil {
state.Client.Log.Warn("body decode: ", err)
return nil
}
state.Client.Log.Debug(auth.Mechanism, string(data))
state.Client.Log.Info("success auth")
fmt.Fprintf(state.Client.Conn, "<success xmlns='%s'/>", xmpp.NSSASL)
return state.Next
}

View File

@ -1,68 +0,0 @@
package utils
import (
"encoding/xml"
"net"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/yaja/model"
"dev.sum7.eu/genofire/yaja/xmpp/base"
)
type Client struct {
Log *log.Entry
Conn net.Conn
Out *xml.Encoder
In *xml.Decoder
JID *xmppbase.JID
account *model.Account
Messages chan interface{}
close chan interface{}
}
func NewClient(conn net.Conn, level log.Level) *Client {
logger := log.New()
logger.SetLevel(level)
client := &Client{
Conn: conn,
Log: log.NewEntry(logger),
In: xml.NewDecoder(conn),
Out: xml.NewEncoder(conn),
Messages: make(chan interface{}),
close: make(chan interface{}),
}
return client
}
func (client *Client) SetConnecting(conn net.Conn) {
client.Conn = conn
client.In = xml.NewDecoder(conn)
client.Out = xml.NewEncoder(conn)
}
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) OnClose() <-chan interface{} {
return client.close
}
func (client *Client) Close() {
client.close <- true
client.Conn.Close()
}

View File

@ -1,5 +0,0 @@
package utils
import "dev.sum7.eu/genofire/yaja/xmpp/base"
type DomainRegisterAllowed func(*xmppbase.JID) bool

3
yaja-bot_example.conf Normal file
View File

@ -0,0 +1,3 @@
log_level = "debug"
jid = "bot@chat.sum7.eu"
password = "test"

View File

@ -1,16 +0,0 @@
tlsdir = "tmp/ssl"
state_path = "tmp/yaja.json"
[logging]
level = 5
level_client = 6
level_server = 6
[register]
enable = true
domains = []
[address]
webserver = [":https"]
client = [":5222"]
server = [":5269"]