diff --git a/component/receiver.go b/component/receiver.go index 0c8558e..42e4c5d 100644 --- a/component/receiver.go +++ b/component/receiver.go @@ -72,7 +72,7 @@ func (c *Config) handleDiscoItems(s xmpp.Sender, p stanza.Packet) { log.WithFields(map[string]interface{}{ "type": c.Type, - "from": s, + "id": attrs.Id, "to": attrs.To, }).Debug("disco items") s.Send(iq) @@ -92,28 +92,32 @@ func (c *Config) handleIQ(s xmpp.Sender, p stanza.Packet) { log.WithFields(map[string]interface{}{ "type": c.Type, - "from": s, "to": attrs.To, - }).Debugf("ignore: %s", iq.Payload) + "id": attrs.Id, + }).Debugf("ignore: %v", iq.Payload) s.Send(resp) } func (c *Config) handleMessage(s xmpp.Sender, p stanza.Packet) { msg, ok := p.(stanza.Message) - attr := msg.Attrs + attrs := msg.Attrs if !ok { - pr, ok := p.(stanza.Message) - attr = pr.Attrs + pr, ok := p.(stanza.Presence) + attrs = pr.Attrs if !ok { return } } + logger := log.WithFields(map[string]interface{}{ + "type": c.Type, + "id": attrs.Id, + "to": attrs.To, + }) + if attrs.Type == "error" { + logger.Error(msg.XMPPFormat()) + return + } if c.XMPPDebug { - log.WithFields(map[string]interface{}{ - "type": c.Type, - "from": s, - "to": attr.To, - "id": attr.Id, - }).Debug(msg.XMPPFormat()) + logger.Debug(msg.XMPPFormat()) } c.comp.Send(p) } diff --git a/component/send.go b/component/send.go index a2a2c3c..fee6c8f 100644 --- a/component/send.go +++ b/component/send.go @@ -16,16 +16,32 @@ func (c *Config) sender(packets chan stanza.Packet) { } } +func (c *Config) fixAddr(addr string) string { + if addr == "" { + return c.Host + } + if strings.Contains(addr, "{{DOMAIN}}") { + return strings.Replace(addr, "{{DOMAIN}}", c.Host, 1) + } + if !strings.Contains(addr, "@") { + return addr + "@" + c.Host + } + return addr +} + func (c *Config) sending(packet stanza.Packet) stanza.Packet { logger := log.WithField("type", c.Type) switch p := packet.(type) { + case stanza.Presence: + p.From = c.fixAddr(p.From) + if p.To != "" { + p.To = c.fixAddr(p.To) + } + return p case stanza.Message: - if p.From == "" { - p.From = c.Host - } else if strings.Contains(p.From, "{{DOMAIN}}") { - p.From = strings.Replace(p.From, "{{DOMAIN}}", c.Host, 1) - } else { - p.From += "@" + c.Host + p.From = c.fixAddr(p.From) + if p.To != "" { + p.To = c.fixAddr(p.To) } if c.XMPPDebug { logger.WithFields(map[string]interface{}{ diff --git a/component/threema/account.go b/component/threema/account.go index bbb01fd..9740ad9 100644 --- a/component/threema/account.go +++ b/component/threema/account.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/o3ma/o3" + "gosrc.io/xmpp/stanza" "dev.sum7.eu/genofire/golang-lib/database" @@ -16,8 +17,10 @@ type Account struct { ThreemaID *o3.ThreemaID send chan<- o3.Message receive <-chan o3.ReceivedMsg + xmpp chan<- stanza.Packet deliveredMSG map[uint64]string readedMSG map[uint64]string + XMPPResource map[string]map[string]bool } func (t *Threema) getAccount(jid *models.JID) (*Account, error) { @@ -48,6 +51,10 @@ func (t *Threema) getAccount(jid *models.JID) (*Account, error) { AccountThreema: account, ThreemaID: &tid, threema: t, + xmpp: t.out, + XMPPResource: make(map[string]map[string]bool), + deliveredMSG: make(map[uint64]string), + readedMSG: make(map[uint64]string), } session := o3.NewSessionContext(tid) a.send, a.receive, err = session.Run() @@ -57,10 +64,8 @@ func (t *Threema) getAccount(jid *models.JID) (*Account, error) { } a.XMPP = *jid - a.deliveredMSG = make(map[uint64]string) - a.readedMSG = make(map[uint64]string) - go a.receiver(t.out) + go a.receiver() t.accountJID[jid.String()] = a return a, nil diff --git a/component/threema/group.go b/component/threema/group.go new file mode 100644 index 0000000..6844656 --- /dev/null +++ b/component/threema/group.go @@ -0,0 +1,41 @@ +package threema + +import ( + "encoding/base32" + "fmt" + "strings" + + "github.com/o3ma/o3" + "gosrc.io/xmpp" +) + +func strFromThreemaGroup(header *o3.GroupMessageHeader) string { + cid := strings.ToLower(header.CreatorID.String()) + gid := strings.ToLower(base32.StdEncoding.EncodeToString(header.GroupID[:])) + return fmt.Sprintf("%s-%s", cid, gid) +} +func jidFromThreemaGroup(sender string, header *o3.GroupMessageHeader) string { + return fmt.Sprintf("%s@{{DOMAIN}}/%s", strFromThreemaGroup(header), sender) +} +func jidToThreemaGroup(jidS string) (string, *o3.GroupMessageHeader) { + jid, err := xmpp.NewJid(jidS) + if err != nil { + return "", nil + } + node := strings.ToUpper(jid.Node) + a := strings.SplitN(node, "-", 2) + if len(a) != 2 { + return "", nil + } + header := &o3.GroupMessageHeader{ + CreatorID: o3.NewIDString(a[0]), + } + + result, err := base32.StdEncoding.DecodeString(a[1]) + if err != nil { + return "", nil + } + copy(header.GroupID[:], []byte(result)) + + return jid.Resource, header +} diff --git a/component/threema/main.go b/component/threema/main.go index 43406ef..c4377c5 100644 --- a/component/threema/main.go +++ b/component/threema/main.go @@ -68,11 +68,19 @@ func (t *Threema) Send(packet stanza.Packet) { func (t *Threema) send(packet stanza.Packet) stanza.Packet { switch p := packet.(type) { case stanza.Presence: - log.Debug(p) + from := models.ParseJID(p.Attrs.From) + account, err := t.getAccount(from) + if err != nil { + msg := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, To: from.String()}) + msg.Body = "It was not possible to send, because we have no account for you.\nPlease generate one, by sending `generate` to this gateway" + return msg + } + account.handlePresence(p) return nil case stanza.Message: from := models.ParseJID(p.Attrs.From) to := models.ParseJID(p.Attrs.To) + if p.Attrs.Type == stanza.MessageTypeError { msg := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, To: from.String()}) if p.Error.Text == "User session not found" { diff --git a/component/threema/receive.go b/component/threema/receive.go index 7ba463d..8aa2ea7 100644 --- a/component/threema/receive.go +++ b/component/threema/receive.go @@ -1,7 +1,6 @@ package threema import ( - "encoding/base32" "errors" "fmt" "strconv" @@ -9,16 +8,17 @@ import ( "github.com/bdlm/log" "github.com/o3ma/o3" + "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" ) -func (a *Account) receiver(out chan<- stanza.Packet) { +func (a *Account) receiver() { for receivedMessage := range a.receive { if receivedMessage.Err != nil { log.Warnf("Error Receiving Message: %s\n", receivedMessage.Err) xMSG := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, To: a.XMPP.String()}) xMSG.Body = fmt.Sprintf("error on decoding message:\n%v", receivedMessage.Err) - out <- xMSG + a.xmpp <- xMSG continue } header := receivedMessage.Msg.Header() @@ -26,12 +26,36 @@ func (a *Account) receiver(out chan<- stanza.Packet) { if string(a.TID) == sender { continue } - if p, err := a.receiving(receivedMessage.Msg); err != nil { + if p, gh, err := a.receiving(receivedMessage.Msg); err != nil { xMSG := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: sender, To: a.XMPP.String()}) xMSG.Body = fmt.Sprintf("error on decoding message: %s\n%v", err, receivedMessage.Msg) - out <- xMSG - } else if p != nil { - out <- p + a.xmpp <- xMSG + } else { + if gh != nil { + xid := &xmpp.Jid{ + Node: a.XMPP.Local, + Domain: a.XMPP.Domain, + } + id := strFromThreemaGroup(gh) + if len(a.XMPPResource[id]) == 0 { + xMSG := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: sender, To: a.XMPP.String()}) + //TODO please join + xMSG.Body = fmt.Sprintf(`ERROR: group message not delievered, please join +xmpp:%s@{{DOMAIN}}?join`, id) + a.xmpp <- xMSG + continue + } + for r := range a.XMPPResource[id] { + xid.Resource = r + switch m := p.(type) { + case stanza.Message: + m.Attrs.To = xid.Full() + a.xmpp <- m + } + } + } else { + a.xmpp <- p + } } } } @@ -42,13 +66,7 @@ func requestExtensions(xMSG *stanza.Message) { xMSG.Extensions = append(xMSG.Extensions, stanza.StateActive{}) } -func jidFromThreemaGroup(sender string, header *o3.GroupMessageHeader) string { - cid := strings.ToLower(header.CreatorID.String()) - gid := strings.ToLower(base32.StdEncoding.EncodeToString(header.GroupID[:])) - return fmt.Sprintf("%s-%s@{{DOMAIN}}/%s", cid, gid, sender) -} - -func (a *Account) receiving(receivedMessage o3.Message) (stanza.Packet, error) { +func (a *Account) receiving(receivedMessage o3.Message) (stanza.Packet, *o3.GroupMessageHeader, error) { header := receivedMessage.Header() sender := header.Sender.String() logger := log.WithFields(map[string]interface{}{ @@ -62,53 +80,65 @@ func (a *Account) receiving(receivedMessage o3.Message) (stanza.Packet, error) { dbText := "recv text" xMSG := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: sender, To: a.XMPP.String(), Id: strconv.FormatUint(header.ID, 10)}) if msg.GroupMessageHeader != nil { - xMSG = stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeGroupchat, From: jidFromThreemaGroup(sender, msg.GroupMessageHeader), To: a.XMPP.String(), Id: strconv.FormatUint(header.ID, 10)}) + to := a.XMPP.String() + ad := strings.SplitN(msg.Body, "=", 2) + from := sender + if len(ad) == 2 { + from = strings.ToLower(ad[0])[:len(ad[0])-3] + } + xMSG = stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeGroupchat, From: jidFromThreemaGroup(from, msg.GroupMessageHeader), To: to, Id: strconv.FormatUint(header.ID, 10)}) + if len(ad) == 2 { + xMSG.Body = ad[1][4:] + } else { + xMSG.Body = msg.Body + } dbText = "recv grouptext" } else { + xMSG.Body = msg.Body requestExtensions(&xMSG) } - xMSG.Body = msg.Body logger.WithFields(map[string]interface{}{ "from_x": xMSG.From, "id": xMSG.Id, "text": xMSG.Body, }).Debug(dbText) - return xMSG, nil + return xMSG, msg.GroupMessageHeader, nil case *o3.AudioMessage: if a.threema.httpUploadPath == "" { - return nil, errors.New("no place to store files at transport configurated") + return nil, nil, errors.New("no place to store files at transport configurated") } data, err := msg.GetData() if err != nil { logger.Warnf("unable to read data from message: %s", err) - return nil, err + return nil, nil, err } xMSG, err := a.FileToXMPP(sender, header.ID, "mp3", data) if err != nil { logger.Warnf("unable to create data from message: %s", err) - return nil, err + return nil, nil, err } requestExtensions(&xMSG) logger.WithField("url", xMSG.Body).Debug("recv audio") - return xMSG, nil + return xMSG, nil, nil case *o3.ImageMessage: if a.threema.httpUploadPath == "" { - return nil, errors.New("no place to store files at transport configurated") + return nil, nil, errors.New("no place to store files at transport configurated") } data, err := msg.GetData(a.ThreemaID) if err != nil { logger.Warnf("unable to read data from message: %s", err) - return nil, err + return nil, nil, err } xMSG, err := a.FileToXMPP(sender, header.ID, "jpg", data) if err != nil { logger.Warnf("unable to create data from message: %s", err) - return nil, err + return nil, nil, err } requestExtensions(&xMSG) logger.WithField("url", xMSG.Body).Debug("recv image") - return xMSG, nil + return xMSG, nil, nil + case *o3.DeliveryReceiptMessage: xMSG := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: sender, To: a.XMPP.String()}) state := "" @@ -135,9 +165,9 @@ func (a *Account) receiving(receivedMessage o3.Message) (stanza.Packet, error) { if len(xMSG.Extensions) > 0 { logger.WithField("state", state).Debug("recv state") - return xMSG, nil + return xMSG, nil, nil } - return nil, nil + return nil, nil, nil case *o3.TypingNotificationMessage: xMSG := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, From: sender, To: a.XMPP.String(), Id: strconv.FormatUint(header.ID, 10)}) if msg.OnOff != 0 { @@ -146,7 +176,7 @@ func (a *Account) receiving(receivedMessage o3.Message) (stanza.Packet, error) { xMSG.Extensions = append(xMSG.Extensions, stanza.StateInactive{}) } logger.WithField("on", msg.OnOff).Debug("recv typing") - return xMSG, nil + return xMSG, nil, nil } - return nil, errors.New("not known data format") + return nil, nil, errors.New("not known data format") } diff --git a/component/threema/receive_test.go b/component/threema/receive_test.go index 4f66265..2ac3acd 100644 --- a/component/threema/receive_test.go +++ b/component/threema/receive_test.go @@ -20,6 +20,7 @@ func createDummyAccount() Account { a := Account{ deliveredMSG: make(map[uint64]string), readedMSG: make(map[uint64]string), + xmpp: make(chan<- stanza.Packet), } a.TID = make([]byte, len(threemaFromIDByte)) copy(a.TID, threemaFromIDByte[:]) diff --git a/component/threema/send.go b/component/threema/send.go index 05c2de3..c70c603 100644 --- a/component/threema/send.go +++ b/component/threema/send.go @@ -2,14 +2,73 @@ package threema import ( "encoding/base32" + "encoding/xml" "strconv" "strings" "github.com/bdlm/log" "github.com/o3ma/o3" + "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" ) +type PresMUCUserItem struct { + XMLName xml.Name `xml:"item"` + Affiliation string `xml:"affiliation,attr"` + Role string `xml:"role,attr"` +} +type PresMUCUserStatus struct { + XMLName xml.Name `xml:"status"` + Code int `xml:"code,attr"` +} + +type PresMUCUserList struct { + XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` + Items []PresMUCUserItem + Status *PresMUCUserStatus +} + +func (a *Account) handlePresence(p stanza.Presence) error { + logger := log.WithFields(map[string]interface{}{ + "from": p.Attrs.From, + "to": p.Attrs.To, + }) + _, header := jidToThreemaGroup(p.To) + if header == nil { + logger.Debug("no group presence") + return nil + } + + from, _ := xmpp.NewJid(p.From) + to, _ := xmpp.NewJid(p.To) + + if a.XMPPResource[to.Node] == nil { + a.XMPPResource[to.Node] = make(map[string]bool) + } + a.XMPPResource[to.Node][from.Resource] = true + + ownsender := strings.ToLower(a.ThreemaID.ID.String()) + //TODO list current users + senders := []string{header.CreatorID.String(), ownsender} + for _, sender := range senders { + sender = strings.ToLower(sender) + pres := stanza.NewPresence(stanza.Attrs{To: p.Attrs.From, From: jidFromThreemaGroup(sender, header)}) + presMUCUserList := PresMUCUserList{ + Items: []PresMUCUserItem{ + { + Affiliation: "admin", + Role: "moderator", + }, + }, + } + if sender == ownsender { + presMUCUserList.Status = &PresMUCUserStatus{Code: 110} + } + pres.Extensions = append(pres.Extensions, presMUCUserList) + a.xmpp <- pres + } + return nil +} func (a *Account) Send(to string, msg stanza.Message) error { m, err := a.sending(to, msg) if err != nil { @@ -125,6 +184,7 @@ func (a *Account) sending(to string, msg stanza.Message) (o3.Message, error) { if groupHeader != nil { logger.Debug("send grouptext") // TODO iterate of all occupants + //msg3.GroupMessageHeader.Recipient: o3.NewIDString(to), return msg3, nil } a.deliveredMSG[msg3ID] = msg.Id