replace server with bot library
This commit is contained in:
parent
0ad62245e8
commit
7f4f6d5f71
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package daemon
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dev.sum7.eu/genofire/golang-lib/worker"
|
"dev.sum7.eu/genofire/golang-lib/worker"
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package cmd
|
204
daemon/server.go
204
daemon/server.go
|
@ -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")
|
|
||||||
|
|
||||||
}
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
adsa
|
|
|
@ -1 +0,0 @@
|
||||||
package daemon
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
128
server/server.go
128
server/server.go
|
@ -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() {
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import "dev.sum7.eu/genofire/yaja/xmpp/base"
|
|
||||||
|
|
||||||
type DomainRegisterAllowed func(*xmppbase.JID) bool
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
log_level = "debug"
|
||||||
|
jid = "bot@chat.sum7.eu"
|
||||||
|
password = "test"
|
|
@ -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"]
|
|
Reference in New Issue