2020-02-26 15:07:33 +00:00
|
|
|
package main
|
2020-02-16 18:30:49 +00:00
|
|
|
|
|
|
|
import (
|
2020-02-16 22:27:03 +00:00
|
|
|
"fmt"
|
2020-02-26 14:26:57 +00:00
|
|
|
"sync"
|
2020-02-16 22:27:03 +00:00
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
"github.com/hashicorp/golang-lru"
|
2020-02-16 18:30:49 +00:00
|
|
|
"github.com/jinzhu/gorm"
|
|
|
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
|
|
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
|
|
|
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
2020-02-17 18:02:26 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-02-26 14:26:57 +00:00
|
|
|
"golang.org/x/crypto/blake2b"
|
2020-02-17 08:41:08 +00:00
|
|
|
|
|
|
|
"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
2020-02-17 18:02:26 +00:00
|
|
|
"git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
|
2020-02-16 18:30:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var db *gorm.DB
|
2020-02-28 10:06:43 +00:00
|
|
|
var dbCache *lru.TwoQueueCache
|
2020-02-16 18:30:49 +00:00
|
|
|
|
|
|
|
func InitDb() error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
db, err = gorm.Open(config.DbType, config.DbPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-26 19:21:32 +00:00
|
|
|
db.AutoMigrate(&DbAccountConfig{})
|
|
|
|
|
2020-02-28 15:55:45 +00:00
|
|
|
db.AutoMigrate(&DbJoinedRoom{})
|
|
|
|
db.Model(&DbJoinedRoom{}).AddIndex("idx_joined_room_user_protocol_account", "mx_user_id", "protocol", "account_name")
|
2020-02-21 17:43:47 +00:00
|
|
|
|
2020-02-16 21:07:41 +00:00
|
|
|
db.AutoMigrate(&DbUserMap{})
|
2020-02-28 15:55:45 +00:00
|
|
|
db.Model(&DbUserMap{}).AddIndex("idx_user_map_protocol_user", "protocol", "user_id")
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
db.AutoMigrate(&DbRoomMap{})
|
2020-02-28 15:55:45 +00:00
|
|
|
db.Model(&DbRoomMap{}).AddIndex("idx_room_map_protocol_room", "protocol", "room_id")
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
db.AutoMigrate(&DbPmRoomMap{})
|
2020-02-28 15:55:45 +00:00
|
|
|
db.Model(&DbPmRoomMap{}).AddIndex("idx_pm_room_map_protocol_user_account_user", "protocol", "user_id", "mx_user_id", "account_name")
|
2020-02-16 21:07:41 +00:00
|
|
|
|
2020-02-28 15:55:45 +00:00
|
|
|
db.AutoMigrate(&DbKv{})
|
2020-02-26 15:30:10 +00:00
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
dbCache, err = lru.New2Q(10000)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-16 18:30:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-02-16 21:07:41 +00:00
|
|
|
|
2020-02-26 19:21:32 +00:00
|
|
|
// Account configuration
|
|
|
|
type DbAccountConfig struct {
|
|
|
|
gorm.Model
|
|
|
|
|
2020-02-28 15:55:45 +00:00
|
|
|
MxUserID string `gorm:"type:text;index"`
|
|
|
|
Name string `gorm:"type:text"`
|
|
|
|
Protocol string `gorm:"type:text"`
|
|
|
|
Config string `gorm:"type:text"`
|
2020-02-26 19:21:32 +00:00
|
|
|
}
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
// List of joined channels to be re-joined on reconnect
|
|
|
|
type DbJoinedRoom struct {
|
2020-02-28 15:55:45 +00:00
|
|
|
ID uint `gorm:"primary_key"`
|
2020-02-21 17:43:47 +00:00
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
// User id and account name
|
2020-02-28 15:55:45 +00:00
|
|
|
MxUserID string `gorm:"type:text"`
|
|
|
|
Protocol string `gorm:"type:text"`
|
|
|
|
AccountName string `gorm:"type:text"`
|
2020-02-28 10:06:43 +00:00
|
|
|
|
|
|
|
// Room ID
|
2020-02-28 15:55:45 +00:00
|
|
|
RoomID connector.RoomID `gorm:"type:text"`
|
2020-02-21 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-16 21:07:41 +00:00
|
|
|
// User mapping between protocol user IDs and puppeted matrix ids
|
|
|
|
type DbUserMap struct {
|
2020-02-28 15:55:45 +00:00
|
|
|
ID uint `gorm:"primary_key"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
2020-02-28 15:55:45 +00:00
|
|
|
Protocol string `gorm:"type:text"`
|
|
|
|
UserID connector.UserID `gorm:"type:text"`
|
|
|
|
MxUserID string `gorm:"type:text;index"`
|
2020-02-16 21:07:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Room mapping between Matrix rooms and outside rooms
|
|
|
|
type DbRoomMap struct {
|
2020-02-28 15:55:45 +00:00
|
|
|
ID uint `gorm:"primary_key"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
// Network protocol
|
2020-02-28 15:55:45 +00:00
|
|
|
Protocol string `gorm:"type:text"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
// Room id on the bridged network
|
2020-02-28 15:55:45 +00:00
|
|
|
RoomID connector.RoomID `gorm:"type:text"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
// Bridged room matrix id
|
2020-02-28 15:55:45 +00:00
|
|
|
MxRoomID string `gorm:"type:text;index"`
|
2020-02-16 21:07:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Room mapping between Matrix rooms and private messages
|
|
|
|
type DbPmRoomMap struct {
|
2020-02-28 15:55:45 +00:00
|
|
|
ID uint `gorm:"primary_key"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
// User id and account name of the local end viewed on Matrix
|
2020-02-28 15:55:45 +00:00
|
|
|
MxUserID string `gorm:"type:text"`
|
|
|
|
Protocol string `gorm:"type:text"`
|
|
|
|
AccountName string `gorm:"type:text"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
// User id to reach them
|
2020-02-28 15:55:45 +00:00
|
|
|
UserID connector.UserID `gorm:"type:text"`
|
2020-02-16 21:07:41 +00:00
|
|
|
|
|
|
|
// Bridged room for PMs
|
2020-02-28 15:55:45 +00:00
|
|
|
MxRoomID string `gorm:"type:text;index"`
|
2020-02-16 21:07:41 +00:00
|
|
|
}
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
// Key-value store for various things
|
|
|
|
type DbKv struct {
|
2020-02-28 15:55:45 +00:00
|
|
|
Key string `gorm:"type:text;primary_key"`
|
|
|
|
Value string `gorm:"type:text"`
|
2020-02-26 15:30:10 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 14:26:57 +00:00
|
|
|
// ---- Simple locking mechanism
|
2020-02-28 10:06:43 +00:00
|
|
|
// Slot keys are strings that identify the object we are acting upon
|
|
|
|
// They define which lock to lock for a certain operation
|
|
|
|
// They are also used as keys in the LRU cache
|
2020-02-26 14:26:57 +00:00
|
|
|
|
|
|
|
var dbLocks [256]sync.Mutex
|
|
|
|
|
|
|
|
func dbLockSlot(key string) {
|
|
|
|
slot := blake2b.Sum512([]byte(key))[0]
|
|
|
|
dbLocks[slot].Lock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func dbUnlockSlot(key string) {
|
|
|
|
slot := blake2b.Sum512([]byte(key))[0]
|
|
|
|
dbLocks[slot].Unlock()
|
|
|
|
}
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
// ---- Key-value store supporting atomic test-and-set
|
|
|
|
|
|
|
|
func dbKvSlotKey(key string) string {
|
|
|
|
return "kv:" + key
|
|
|
|
}
|
2020-02-16 22:27:03 +00:00
|
|
|
|
2020-02-28 09:34:22 +00:00
|
|
|
func dbKvGet(key string) string {
|
2020-02-28 10:06:43 +00:00
|
|
|
slot_key := dbKvSlotKey(key)
|
|
|
|
|
|
|
|
if ent, ok := dbCache.Get(slot_key); ok {
|
|
|
|
return ent.(string)
|
|
|
|
}
|
|
|
|
|
2020-02-28 09:34:22 +00:00
|
|
|
var entry DbKv
|
|
|
|
if db.Where(&DbKv{Key: key}).First(&entry).RecordNotFound() {
|
2020-02-21 17:43:47 +00:00
|
|
|
return ""
|
|
|
|
} else {
|
|
|
|
return entry.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-28 09:34:22 +00:00
|
|
|
func dbKvPut(key string, value string) {
|
2020-02-28 10:06:43 +00:00
|
|
|
slot_key := dbKvSlotKey(key)
|
|
|
|
|
|
|
|
dbLockSlot(slot_key)
|
|
|
|
defer dbUnlockSlot(slot_key)
|
|
|
|
|
2020-02-28 09:34:22 +00:00
|
|
|
var entry DbKv
|
|
|
|
db.Where(&DbKv{Key: key}).Assign(&DbKv{Value: value}).FirstOrCreate(&entry)
|
2020-02-28 10:06:43 +00:00
|
|
|
dbCache.Add(slot_key, value)
|
2020-02-21 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-28 09:34:22 +00:00
|
|
|
func dbKvTestAndSet(key string, value string) bool {
|
2020-02-28 10:06:43 +00:00
|
|
|
slot_key := dbKvSlotKey(key)
|
|
|
|
|
|
|
|
dbLockSlot(slot_key)
|
|
|
|
defer dbUnlockSlot(slot_key)
|
2020-02-26 14:26:57 +00:00
|
|
|
|
2020-02-21 17:43:47 +00:00
|
|
|
// True if value was changed, false if was already set
|
2020-02-28 10:06:43 +00:00
|
|
|
if dbKvGet(key) == value {
|
|
|
|
return false
|
2020-02-21 17:43:47 +00:00
|
|
|
}
|
2020-02-28 10:06:43 +00:00
|
|
|
|
|
|
|
var entry DbKv
|
|
|
|
db.Where(&DbKv{Key: key}).Assign(&DbKv{Value: value}).FirstOrCreate(&entry)
|
|
|
|
dbCache.Add(slot_key, value)
|
|
|
|
return true
|
2020-02-21 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
// ----
|
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
func dbGetMxRoom(protocol string, roomId connector.RoomID) (string, error) {
|
2020-02-28 10:06:43 +00:00
|
|
|
slot_key := fmt.Sprintf("room:%s/%s", protocol, roomId)
|
|
|
|
|
2020-02-26 14:26:57 +00:00
|
|
|
dbLockSlot(slot_key)
|
|
|
|
defer dbUnlockSlot(slot_key)
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
if cached, ok := dbCache.Get(slot_key); ok {
|
|
|
|
return cached.(string), nil
|
|
|
|
}
|
2020-02-16 22:27:03 +00:00
|
|
|
|
|
|
|
// Check if room exists in our mapping,
|
|
|
|
// If not create it
|
2020-02-28 10:06:43 +00:00
|
|
|
var room DbRoomMap
|
2020-02-16 22:27:03 +00:00
|
|
|
must_create := db.First(&room, DbRoomMap{
|
|
|
|
Protocol: protocol,
|
2020-02-17 18:02:26 +00:00
|
|
|
RoomID: roomId,
|
2020-02-16 22:27:03 +00:00
|
|
|
}).RecordNotFound()
|
2020-02-28 10:06:43 +00:00
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
if must_create {
|
|
|
|
alias := roomAlias(protocol, roomId)
|
|
|
|
// Lookup alias
|
2020-02-21 13:27:42 +00:00
|
|
|
mx_room_id, err := mx.DirectoryRoom(fmt.Sprintf("#%s:%s", alias, config.MatrixDomain))
|
2020-02-16 22:27:03 +00:00
|
|
|
|
|
|
|
// If no alias found, create room
|
|
|
|
if err != nil {
|
|
|
|
name := fmt.Sprintf("%s (%s)", roomId, protocol)
|
|
|
|
|
2020-02-21 13:27:42 +00:00
|
|
|
mx_room_id, err = mx.CreateRoom(name, alias, []string{})
|
2020-02-16 22:27:03 +00:00
|
|
|
if err != nil {
|
2020-02-27 09:35:09 +00:00
|
|
|
log.Warnf("Could not create room for %s: %s", name, err)
|
2020-02-16 22:27:03 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
room = DbRoomMap{
|
|
|
|
Protocol: protocol,
|
2020-02-17 18:02:26 +00:00
|
|
|
RoomID: roomId,
|
2020-02-16 22:27:03 +00:00
|
|
|
MxRoomID: mx_room_id,
|
|
|
|
}
|
|
|
|
db.Create(&room)
|
|
|
|
}
|
2020-02-28 10:06:43 +00:00
|
|
|
|
|
|
|
log.Tracef("%s -> %s", slot_key, room.MxRoomID)
|
|
|
|
dbCache.Add(slot_key, room.MxRoomID)
|
2020-02-16 22:27:03 +00:00
|
|
|
|
|
|
|
return room.MxRoomID, nil
|
|
|
|
}
|
|
|
|
|
2020-02-28 11:16:59 +00:00
|
|
|
func dbPmRoomSlotKey(room *DbPmRoomMap) string {
|
|
|
|
return fmt.Sprintf("pmroom:%s/%s/%s/%s",
|
|
|
|
room.Protocol, room.MxUserID, room.AccountName, room.UserID)
|
|
|
|
}
|
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
func dbGetMxPmRoom(protocol string, them connector.UserID, themMxId string, usMxId string, usAccount string) (string, error) {
|
2020-02-28 11:16:59 +00:00
|
|
|
map_key := &DbPmRoomMap{
|
|
|
|
MxUserID: usMxId,
|
|
|
|
Protocol: protocol,
|
|
|
|
AccountName: usAccount,
|
|
|
|
UserID: them,
|
|
|
|
}
|
|
|
|
slot_key := dbPmRoomSlotKey(map_key)
|
2020-02-28 10:06:43 +00:00
|
|
|
|
2020-02-26 14:26:57 +00:00
|
|
|
dbLockSlot(slot_key)
|
|
|
|
defer dbUnlockSlot(slot_key)
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
if cached, ok := dbCache.Get(slot_key); ok {
|
|
|
|
return cached.(string), nil
|
|
|
|
}
|
2020-02-16 22:27:03 +00:00
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
var room DbPmRoomMap
|
2020-02-28 11:16:59 +00:00
|
|
|
must_create := db.First(&room, map_key).RecordNotFound()
|
2020-02-28 10:06:43 +00:00
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
if must_create {
|
|
|
|
name := fmt.Sprintf("%s (%s)", them, protocol)
|
|
|
|
|
2020-02-21 13:27:42 +00:00
|
|
|
mx_room_id, err := mx.CreateDirectRoomAs([]string{usMxId}, themMxId)
|
2020-02-16 22:27:03 +00:00
|
|
|
if err != nil {
|
2020-02-27 09:35:09 +00:00
|
|
|
log.Warnf("Could not create room for %s: %s", name, err)
|
2020-02-16 22:27:03 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
room = DbPmRoomMap{
|
2020-02-17 18:02:26 +00:00
|
|
|
MxUserID: usMxId,
|
|
|
|
Protocol: protocol,
|
2020-02-16 22:27:03 +00:00
|
|
|
AccountName: usAccount,
|
2020-02-17 18:02:26 +00:00
|
|
|
UserID: them,
|
|
|
|
MxRoomID: mx_room_id,
|
2020-02-16 22:27:03 +00:00
|
|
|
}
|
|
|
|
db.Create(&room)
|
|
|
|
}
|
2020-02-28 10:06:43 +00:00
|
|
|
|
|
|
|
log.Tracef("%s -> %s", slot_key, room.MxRoomID)
|
|
|
|
dbCache.Add(slot_key, room.MxRoomID)
|
2020-02-16 22:27:03 +00:00
|
|
|
|
|
|
|
return room.MxRoomID, nil
|
|
|
|
}
|
|
|
|
|
2020-02-28 11:16:59 +00:00
|
|
|
func dbDeletePmRoom(room *DbPmRoomMap) {
|
|
|
|
slot_key := dbPmRoomSlotKey(room)
|
|
|
|
|
|
|
|
dbLockSlot(slot_key)
|
|
|
|
defer dbUnlockSlot(slot_key)
|
|
|
|
|
|
|
|
db.Delete(room)
|
|
|
|
dbCache.Remove(slot_key)
|
|
|
|
}
|
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
func dbGetMxUser(protocol string, userId connector.UserID) (string, error) {
|
2020-02-28 10:06:43 +00:00
|
|
|
slot_key := fmt.Sprintf("user:%s/%s", protocol, userId)
|
|
|
|
|
2020-02-26 14:26:57 +00:00
|
|
|
dbLockSlot(slot_key)
|
|
|
|
defer dbUnlockSlot(slot_key)
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
if cached, ok := dbCache.Get(slot_key); ok {
|
|
|
|
return cached.(string), nil
|
|
|
|
}
|
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
var user DbUserMap
|
|
|
|
|
|
|
|
must_create := db.First(&user, DbUserMap{
|
|
|
|
Protocol: protocol,
|
2020-02-17 18:02:26 +00:00
|
|
|
UserID: userId,
|
2020-02-16 22:27:03 +00:00
|
|
|
}).RecordNotFound()
|
|
|
|
if must_create {
|
|
|
|
username := userMxId(protocol, userId)
|
|
|
|
|
2020-02-21 13:27:42 +00:00
|
|
|
err := mx.RegisterUser(username)
|
2020-02-16 22:27:03 +00:00
|
|
|
if err != nil {
|
|
|
|
if mxE, ok := err.(*mxlib.MxError); !ok || mxE.ErrCode != "M_USER_IN_USE" {
|
2020-02-27 09:35:09 +00:00
|
|
|
log.Warnf("Could not register %s: %s", username, err)
|
2020-02-16 22:27:03 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mxid := fmt.Sprintf("@%s:%s", username, config.MatrixDomain)
|
2020-02-21 13:27:42 +00:00
|
|
|
mx.ProfileDisplayname(mxid, fmt.Sprintf("%s (%s)", userId, protocol))
|
2020-02-16 22:27:03 +00:00
|
|
|
|
|
|
|
user = DbUserMap{
|
|
|
|
Protocol: protocol,
|
2020-02-17 18:02:26 +00:00
|
|
|
UserID: userId,
|
2020-02-16 22:27:03 +00:00
|
|
|
MxUserID: mxid,
|
|
|
|
}
|
|
|
|
db.Create(&user)
|
|
|
|
}
|
|
|
|
|
2020-02-28 10:06:43 +00:00
|
|
|
log.Tracef("%s -> %s", slot_key, user.MxUserID)
|
|
|
|
dbCache.Add(slot_key, user.MxUserID)
|
|
|
|
|
2020-02-16 22:27:03 +00:00
|
|
|
return user.MxUserID, nil
|
|
|
|
}
|
2020-02-17 14:30:01 +00:00
|
|
|
|
|
|
|
func dbIsPmRoom(mxRoomId string) *DbPmRoomMap {
|
|
|
|
var pm_room DbPmRoomMap
|
|
|
|
if db.First(&pm_room, DbPmRoomMap{MxRoomID: mxRoomId}).RecordNotFound() {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return &pm_room
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func dbIsPublicRoom(mxRoomId string) *DbRoomMap {
|
|
|
|
var room DbRoomMap
|
|
|
|
if db.First(&room, DbRoomMap{MxRoomID: mxRoomId}).RecordNotFound() {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return &room
|
|
|
|
}
|
|
|
|
}
|