diff --git a/component/main.go b/component/main.go index 0919671..9013bee 100644 --- a/component/main.go +++ b/component/main.go @@ -31,6 +31,10 @@ func Load(configs []Config) { log.WithField("type", config.Type).Panic(err) } config.comp = comp + err = config.Start() + if err != nil { + log.WithField("type", config.Type).Panic(err) + } log.WithField("type", config.Type).Infof("component for %s started", config.Host) } } diff --git a/component/threema/account.go b/component/threema/account.go new file mode 100644 index 0000000..b3d2631 --- /dev/null +++ b/component/threema/account.go @@ -0,0 +1,84 @@ +package threema + +import ( + "strconv" + + "github.com/bdlm/log" + "github.com/o3ma/o3" + "gosrc.io/xmpp" + + "dev.sum7.eu/genofire/golang-lib/database" + + "dev.sum7.eu/genofire/thrempp/models" +) + +type Account struct { + models.AccountThreema + Session o3.SessionContext + send chan<- o3.Message + recieve <-chan o3.ReceivedMsg +} + +func (t *Threema) getAccount(jid *models.JID) *Account { + if a, ok := t.accountJID[jid.String()]; ok { + return a + } + account := models.AccountThreema{} + + database.Read.Where("xmpp_id = (?)", + database.Read.Table(jid.TableName()).Select("id").Where(map[string]interface{}{ + "local": jid.Local, + "domain": jid.Domain, + }).QueryExpr()).First(&account) + + var lsk [32]byte + copy(lsk[:], account.LSK[:]) + tid, err := o3.NewThreemaID(string(account.TID), lsk, o3.AddressBook{}) + // TODO error handling + if err != nil { + return nil + } + tid.Nick = o3.NewPubNick("xmpp:" + jid.String()) + + a := &Account{AccountThreema: account} + a.XMPP = *jid + a.Session = o3.NewSessionContext(tid) + a.send, a.recieve, err = a.Session.Run() + + // TODO error handling + if err != nil { + return nil + } + + go a.reciever(t.out) + + t.accountJID[jid.String()] = a + t.accountTID[string(a.TID)] = a + return a +} + +func (a *Account) reciever(out chan<- xmpp.Packet) { + for receivedMessage := range a.recieve { + if receivedMessage.Err != nil { + log.Warnf("Error Receiving Message: %s\n", receivedMessage.Err) + continue + } + switch msg := receivedMessage.Msg.(type) { + case o3.TextMessage: + sender := msg.Sender().String() + if string(a.TID) == sender { + continue + } + xMSG := xmpp.NewMessage("chat", sender, a.XMPP.String(), strconv.FormatUint(msg.ID(), 10), "en") + xMSG.Body = msg.Text() + out <- xMSG + case o3.DeliveryReceiptMessage: + // msg.MsgID() + + } + } +} + +func (a *Account) Send(to string, msg string) error { + return a.Session.SendTextMessage(to, msg, a.send) +} diff --git a/component/threema/bot.go b/component/threema/bot.go new file mode 100644 index 0000000..7edcc61 --- /dev/null +++ b/component/threema/bot.go @@ -0,0 +1,61 @@ +package threema + +import ( + "fmt" + + "github.com/bdlm/log" + "github.com/o3ma/o3" + + "dev.sum7.eu/genofire/golang-lib/database" + + "dev.sum7.eu/genofire/thrempp/models" +) + +func (t *Threema) Bot(from *models.JID, request string) string { + server := o3.ThreemaRest{} + logger := log.WithFields(map[string]interface{}{ + "type": "threema", + "jid": from.String(), + }) + + switch request { + case "generate": + + // test if account already exists + account := t.getAccount(from) + if account != nil { + return fmt.Sprintf("you already has the threema account with id: %s", string(account.TID)) + } + + // create account + id, err := server.CreateIdentity() + if err != nil { + logger.Warnf("failed to generate: %s", err) + return fmt.Sprintf("failed to create a threema account: %s", err) + } + //TODO works it + if err := database.Read.Where(from).First(from); err != nil { + database.Write.Create(from) + } + + // store account + a := models.AccountThreema{} + a.XMPPID = from.ID + a.TID = make([]byte, len(id.ID)) + a.LSK = make([]byte, len(id.LSK)) + copy(a.TID, id.ID[:]) + copy(a.LSK, id.LSK[:]) + database.Write.Create(&a) + + // fetch account and connect + account = t.getAccount(from) + tid := string(account.TID) + if tid != "" { + logger.WithField("threema", tid).Info("generate") + return fmt.Sprintf("threema account with id: %s", tid) + } + logger.Warn("failed to generate") + return "failed to create a threema account" + } + return "command not supported" +} diff --git a/component/threema/main.go b/component/threema/main.go index 0616aac..46015ba 100644 --- a/component/threema/main.go +++ b/component/threema/main.go @@ -1,13 +1,79 @@ package threema -import "dev.sum7.eu/genofire/thrempp/component" +import ( + "strings" + + "github.com/bdlm/log" + "gosrc.io/xmpp" + + "dev.sum7.eu/genofire/golang-lib/database" + + "dev.sum7.eu/genofire/thrempp/component" + "dev.sum7.eu/genofire/thrempp/models" +) type Threema struct { component.Component + out chan xmpp.Packet + accountJID map[string]*Account + accountTID map[string]*Account } func NewThreema(config map[string]interface{}) (component.Component, error) { - return &Threema{}, nil + return &Threema{ + out: make(chan xmpp.Packet), + accountJID: make(map[string]*Account), + accountTID: make(map[string]*Account), + }, nil +} + +func (t *Threema) Connect() (chan xmpp.Packet, error) { + var jids []*models.JID + database.Read.Find(&jids) + for _, jid := range jids { + a := t.getAccount(jid) + log.WithFields(map[string]interface{}{ + "jid": jid.String(), + "threema": string(a.TID), + }).Debug("connected") + } + return t.out, nil +} +func (t *Threema) Send(packet xmpp.Packet) { + switch p := packet.(type) { + case xmpp.Message: + from := models.ParseJID(p.PacketAttrs.From) + to := models.ParseJID(p.PacketAttrs.To) + + logger := log.WithFields(map[string]interface{}{ + "from": from, + "to": to, + }) + logger.Debug(p.Body) + if to.IsDomain() { + msg := xmpp.NewMessage("chat", "", from.String(), "", "en") + msg.Body = t.Bot(from, p.Body) + t.out <- msg + return + } + + account := t.getAccount(from) + if account == nil { + msg := xmpp.NewMessage("chat", "", from.String(), "", "en") + msg.Body = "It was not possible to send, becouse we have no account for you.\nPlease generate one, by sending `generate` to this gateway" + t.out <- msg + return + } + + threemaID := strings.ToUpper(to.Local) + if err := account.Send(threemaID, p.Body); err != nil { + msg := xmpp.NewMessage("chat", "", from.String(), "", "en") + msg.Body = err.Error() + t.out <- msg + } + default: + log.Warnf("unkown package%v", p) + } } func init() { diff --git a/component/xmpp.go b/component/xmpp.go index 02a4ca6..0ae5ada 100644 --- a/component/xmpp.go +++ b/component/xmpp.go @@ -1,6 +1,7 @@ package component import ( + "github.com/bdlm/log" "gosrc.io/xmpp" ) @@ -32,7 +33,104 @@ func (c *Config) Start() error { return nil } -func (c *Config) recieve(chan xmpp.Packet) { +func (c *Config) recieve(packets chan xmpp.Packet) { + logger := log.WithField("type", c.Type) + for packet := range packets { + switch p := packet.(type) { + case xmpp.Message: + if p.PacketAttrs.From == "" { + p.PacketAttrs.From = c.Host + } else { + p.PacketAttrs.From += "@" + c.Host + } + loggerMSG := logger.WithFields(map[string]interface{}{ + "from": p.PacketAttrs.From, + "to": p.PacketAttrs.To, + }) + loggerMSG.Debug(p.Body) + c.xmpp.Send(p) + default: + log.Warn("ignoring packet:", packet) + } + } } func (c *Config) sender() { + logger := log.WithField("type", c.Type) + for { + packet, err := c.xmpp.ReadPacket() + if err != nil { + logger.Panicf("connection closed%s", err) + return + } + + switch p := packet.(type) { + case xmpp.IQ: + attrs := p.PacketAttrs + loggerIQ := logger.WithFields(map[string]interface{}{ + "from": attrs.From, + "to": attrs.To, + }) + + switch inner := p.Payload[0].(type) { + case *xmpp.DiscoInfo: + loggerIQ.Debug("Disco Info") + if p.Type == "get" { + iq := xmpp.NewIQ("result", attrs.To, attrs.From, attrs.Id, "en") + var identity xmpp.Identity + if inner.Node == "" { + identity = xmpp.Identity{ + Name: c.Type, + Category: "gateway", + Type: "service", + } + } + + payload := xmpp.DiscoInfo{ + Identity: identity, + Features: []xmpp.Feature{ + {Var: "http://jabber.org/protocol/disco#info"}, + {Var: "http://jabber.org/protocol/disco#item"}, + }, + } + iq.AddPayload(&payload) + + _ = c.xmpp.Send(iq) + } + case *xmpp.DiscoItems: + loggerIQ.Debug("DiscoItems") + if p.Type == "get" { + iq := xmpp.NewIQ("result", attrs.To, attrs.From, attrs.Id, "en") + + var payload xmpp.DiscoItems + if inner.Node == "" { + payload = xmpp.DiscoItems{ + Items: []xmpp.DiscoItem{ + {Name: c.Type, JID: c.Host, Node: "node1"}, + }, + } + } + iq.AddPayload(&payload) + _ = c.xmpp.Send(iq) + } + default: + logger.Debug("ignoring iq packet", inner) + xError := xmpp.Err{ + Code: 501, + Reason: "feature-not-implemented", + Type: "cancel", + } + reply := p.MakeError(xError) + _ = c.xmpp.Send(&reply) + } + + case xmpp.Message: + c.comp.Send(packet) + + case xmpp.Presence: + logger.Debug("Received presence:", p.Type) + + default: + logger.Debug("ignoring packet:", packet) + } + } } diff --git a/models/jid.go b/models/jid.go index b42a2aa..7ce2304 100644 --- a/models/jid.go +++ b/models/jid.go @@ -1,6 +1,8 @@ package models import ( + "regexp" + "github.com/jinzhu/gorm" "dev.sum7.eu/genofire/golang-lib/database" @@ -16,6 +18,48 @@ func (j *JID) TableName() string { return "jid" } +func ParseJID(jidString string) (jid *JID) { + jidSplitTmp := jidRegex.FindAllStringSubmatch(jidString, -1) + + if len(jidSplitTmp) != 1 { + return nil + } + jidSplit := jidSplitTmp[0] + + return &JID{ + Local: jidSplit[1], + Domain: jidSplit[2], + } +} + +func (jid *JID) String() string { + if jid == nil { + return "" + } + str := jid.Domain + if jid.Local != "" { + str = jid.Local + "@" + str + } + return str +} + +func (jid *JID) IsDomain() bool { + return jid != nil && jid.Local == "" && jid.Domain != "" +} + +func GetJID(jidStr string) (jid *JID) { + jidS := ParseJID(jidStr) + err := database.Read.Where(jidS).First(jid).Error + if err != nil { + return nil + } + return +} + +var jidRegex *regexp.Regexp + func init() { + jidRegex = regexp.MustCompile(`^(?:([^@/<>'\" ]+)@)?([^@/<>'\"]+)(?:/([^<>'\" ][^<>'\"]*))?$`) + database.AddModel(&JID{}) }