2017-12-02 07:50:51 +00:00
package main
import (
"encoding/base64"
"fmt"
2017-12-11 11:01:24 +00:00
"log"
2017-12-02 07:50:51 +00:00
"strconv"
"strings"
2017-12-15 01:39:18 +00:00
"time"
2017-12-02 07:50:51 +00:00
"github.com/gorilla/securecookie"
2017-12-11 11:01:24 +00:00
"github.com/jmoiron/sqlx"
2023-03-24 03:47:06 +00:00
_ "github.com/glebarez/go-sqlite"
2017-12-02 07:50:51 +00:00
"github.com/pkg/errors"
)
2021-06-25 06:00:33 +00:00
const databaseVersion = 1
2017-12-02 07:50:51 +00:00
2017-12-11 11:01:24 +00:00
var ErrAccountExists = errors . New ( "account already exists" )
var ErrChannelExists = errors . New ( "channel already exists" )
var ErrChannelDoesNotExist = errors . New ( "channel does not exist" )
2017-12-02 07:50:51 +00:00
var tables = map [ string ] [ ] string {
"meta" : {
"`key` TEXT NULL PRIMARY KEY" ,
"`value` TEXT NULL" } ,
"accounts" : {
"`id` INTEGER PRIMARY KEY AUTOINCREMENT" ,
"`username` TEXT NULL" ,
"`password` TEXT NULL" } ,
"channels" : {
"`channel` TEXT PRIMARY KEY" ,
"`topic` TEXT NULL" ,
"`topictime` INTEGER NULL" ,
"`password` TEXT NULL" } ,
"permissions" : {
"`channel` TEXT NULL" ,
"`account` INTEGER NULL" ,
"`permission` INTEGER NULL" } ,
"bans" : {
"`channel` TEXT NULL" ,
"`type` INTEGER NULL" ,
"`target` TEXT NULL" ,
"`expires` INTEGER NULL" ,
"`reason` TEXT NULL" } }
2017-12-11 11:01:24 +00:00
const (
BAN_TYPE_ADDRESS = 1
BAN_TYPE_ACCOUNT = 2
)
2017-12-02 07:50:51 +00:00
type DBAccount struct {
2017-12-15 01:39:18 +00:00
ID int64
2017-12-11 11:01:24 +00:00
Username string
Password string
2017-12-02 07:50:51 +00:00
}
type DBChannel struct {
Channel string
Topic string
2017-12-15 01:39:18 +00:00
TopicTime int64
2017-12-02 07:50:51 +00:00
Password string
}
type DBPermission struct {
Channel string
2017-12-15 01:39:18 +00:00
Account int64
2017-12-02 07:50:51 +00:00
Permission int
}
2017-12-11 11:01:24 +00:00
type DBMode struct {
Channel string
Mode string
Value string
}
2017-12-02 07:50:51 +00:00
type DBBan struct {
Channel string
Type int
Target string
2017-12-15 01:39:18 +00:00
Expires int64
2017-12-02 07:50:51 +00:00
Reason string
}
type Database struct {
2017-12-11 11:01:24 +00:00
db * sqlx . DB
2017-12-02 07:50:51 +00:00
}
func ( d * Database ) Connect ( driver string , dataSource string ) error {
var err error
2017-12-11 11:01:24 +00:00
d . db , err = sqlx . Connect ( driver , dataSource )
2017-12-02 07:50:51 +00:00
if err != nil {
return errors . Wrapf ( err , "failed to connect to %s database" , driver )
}
err = d . CreateTables ( )
if err != nil {
return errors . Wrap ( err , "failed to create tables" )
}
err = d . Migrate ( )
if err != nil {
return errors . Wrap ( err , "failed to migrate database" )
}
err = d . Initialize ( )
if err != nil {
return errors . Wrap ( err , "failed to initialize database" )
}
return err
}
func ( d * Database ) CreateTables ( ) error {
for tname , tcolumns := range tables {
_ , err := d . db . Exec ( fmt . Sprintf ( "CREATE TABLE IF NOT EXISTS `%s` (%s)" , tname , strings . Join ( tcolumns , "," ) ) )
if err != nil {
return errors . Wrapf ( err , "failed to create %s table" , tname )
}
}
return nil
}
func ( d * Database ) Migrate ( ) error {
rows , err := d . db . Query ( "SELECT `value` FROM meta WHERE `key`=? LIMIT 1" , "version" )
2017-12-11 11:01:24 +00:00
if p ( err ) {
2017-12-02 07:50:51 +00:00
return errors . Wrap ( err , "failed to fetch database version" )
}
version := 0
for rows . Next ( ) {
v := ""
err = rows . Scan ( & v )
if err != nil {
return errors . Wrap ( err , "failed to fetch database version" )
}
version , err = strconv . Atoi ( v )
if err != nil {
version = - 1
}
}
if version == - 1 {
2017-12-11 11:01:24 +00:00
log . Panic ( "Unable to migrate database: database version unknown" )
2017-12-02 07:50:51 +00:00
} else if version == 0 {
2021-06-25 06:00:33 +00:00
_ , err := d . db . Exec ( "UPDATE meta SET `value`=? WHERE `key`=?" , strconv . Itoa ( databaseVersion ) , "version" )
2017-12-02 07:50:51 +00:00
if err != nil {
return errors . Wrap ( err , "failed to save database version" )
}
2021-06-25 06:00:33 +00:00
} else if version < databaseVersion {
// databaseVersion 2 migration queries will go here
2017-12-02 07:50:51 +00:00
}
return nil
}
func ( d * Database ) Initialize ( ) error {
2017-12-11 11:01:24 +00:00
a , err := db . Account ( 1 )
if err != nil {
return errors . Wrap ( err , "failed to initialize" )
}
2017-12-02 07:50:51 +00:00
2017-12-11 11:01:24 +00:00
if a . ID > 0 {
return nil // Admin account exists
}
2017-12-02 07:50:51 +00:00
2017-12-11 11:01:24 +00:00
err = d . AddAccount ( "admin" , "password" )
if err != nil {
return errors . Wrap ( err , "failed to create initial administrator account" )
2017-12-02 07:50:51 +00:00
}
2021-06-25 06:00:33 +00:00
ac := & DBChannel { Channel : channelServer , Topic : "Secret Area of VIP Quality" }
2017-12-11 11:01:24 +00:00
d . AddChannel ( 1 , ac )
2021-06-25 06:00:33 +00:00
uc := & DBChannel { Channel : channelLobby , Topic : "Welcome to AnonIRC" }
2017-12-11 11:01:24 +00:00
d . AddChannel ( 1 , uc )
2017-12-02 07:50:51 +00:00
return nil
}
func ( d * Database ) Close ( ) error {
err := d . db . Close ( )
if err != nil {
err = errors . Wrap ( err , "failed to close database" )
}
return err
}
// Accounts
2017-12-15 01:39:18 +00:00
func ( d * Database ) Account ( id int64 ) ( DBAccount , error ) {
2017-12-11 11:01:24 +00:00
a := DBAccount { }
err := d . db . Get ( & a , "SELECT * FROM accounts WHERE id=? LIMIT 1" , id )
if p ( err ) {
return a , errors . Wrap ( err , "failed to fetch account" )
2017-12-02 07:50:51 +00:00
}
return a , nil
}
2017-12-11 11:01:24 +00:00
func ( d * Database ) AccountU ( username string ) ( DBAccount , error ) {
a := DBAccount { }
err := d . db . Get ( & a , "SELECT * FROM accounts WHERE username=? LIMIT 1" , generateHash ( username ) )
if p ( err ) {
return a , errors . Wrap ( err , "failed to fetch account by username" )
2017-12-02 07:50:51 +00:00
}
return a , nil
}
// TODO: Lockout on too many failed attempts
2017-12-15 01:39:18 +00:00
func ( d * Database ) Auth ( username string , password string ) ( int64 , error ) {
2017-12-02 07:50:51 +00:00
// TODO: Salt in config
2017-12-11 11:01:24 +00:00
a := DBAccount { }
err := d . db . Get ( & a , "SELECT * FROM accounts WHERE username=? AND password=? LIMIT 1" , generateHash ( username ) , generateHash ( username + "-" + password ) )
if p ( err ) {
2017-12-02 07:50:51 +00:00
return 0 , errors . Wrap ( err , "failed to authenticate account" )
}
2017-12-11 11:01:24 +00:00
return a . ID , nil
2017-12-02 07:50:51 +00:00
}
func ( d * Database ) GenerateToken ( ) string {
return base64 . URLEncoding . EncodeToString ( securecookie . GenerateRandomKey ( 64 ) )
}
func ( d * Database ) AddAccount ( username string , password string ) error {
ex , err := d . AccountU ( username )
if err != nil {
return errors . Wrap ( err , "failed to search for existing account while adding account" )
2017-12-11 11:01:24 +00:00
} else if ex . ID > 0 {
2017-12-02 07:50:51 +00:00
return ErrAccountExists
}
_ , err = d . db . Exec ( "INSERT INTO accounts (username, password) VALUES (?, ?)" , generateHash ( username ) , generateHash ( username + "-" + password ) )
if err != nil {
return errors . Wrap ( err , "failed to add account" )
}
return nil
}
2017-12-15 01:39:18 +00:00
func ( d * Database ) SetUsername ( accountid int64 , username string , password string ) error {
2017-12-02 07:50:51 +00:00
ex , err := d . AccountU ( username )
if err != nil {
return errors . Wrap ( err , "failed to search for existing account while setting username" )
2017-12-11 11:01:24 +00:00
} else if ex . ID > 0 {
2017-12-02 07:50:51 +00:00
return ErrAccountExists
}
_ , err = d . db . Exec ( "UPDATE accounts SET username=?, password=? WHERE id=?" , generateHash ( username ) , generateHash ( username + "-" + password ) , accountid )
if err != nil {
return errors . Wrap ( err , "failed to set username" )
}
return nil
}
2017-12-15 01:39:18 +00:00
func ( d * Database ) SetPassword ( accountid int64 , username string , password string ) error {
2017-12-02 07:50:51 +00:00
_ , err := d . db . Exec ( "UPDATE accounts SET password=? WHERE id=?" , generateHash ( username + "-" + password ) , accountid )
if err != nil {
return errors . Wrap ( err , "failed to set password" )
}
return nil
}
// Channels
2017-12-15 01:39:18 +00:00
func ( d * Database ) ChannelID ( id int64 ) ( DBChannel , error ) {
2017-12-11 11:01:24 +00:00
c := DBChannel { }
err := d . db . Get ( & c , "SELECT * FROM channels WHERE id=? LIMIT 1" , id )
if p ( err ) {
return c , errors . Wrap ( err , "failed to fetch channel" )
2017-12-02 07:50:51 +00:00
}
return c , nil
}
2017-12-11 11:01:24 +00:00
func ( d * Database ) Channel ( channel string ) ( DBChannel , error ) {
c := DBChannel { }
err := d . db . Get ( & c , "SELECT * FROM channels WHERE channel=? LIMIT 1" , generateHash ( channel ) )
if p ( err ) {
return c , errors . Wrap ( err , "failed to fetch channel by key" )
2017-12-02 07:50:51 +00:00
}
return c , nil
}
2017-12-15 01:39:18 +00:00
func ( d * Database ) AddChannel ( accountid int64 , channel * DBChannel ) error {
2017-12-02 07:50:51 +00:00
ex , err := d . Channel ( channel . Channel )
if err != nil {
return errors . Wrap ( err , "failed to search for existing channel while adding channel" )
2017-12-11 11:01:24 +00:00
} else if ex . Channel != "" {
2017-12-02 07:50:51 +00:00
return ErrChannelExists
}
chch := channel . Channel
channel . Channel = generateHash ( strings . ToLower ( channel . Channel ) )
_ , err = d . db . Exec ( "INSERT INTO channels (channel, topic, topictime, password) VALUES (?, ?, ?, ?)" , channel . Channel , channel . Topic , channel . TopicTime , channel . Password )
if err != nil {
return errors . Wrap ( err , "failed to add channel" )
}
2021-06-25 06:00:33 +00:00
err = d . SetPermission ( accountid , chch , permissionSuperAdmin )
2017-12-02 07:50:51 +00:00
if err != nil {
return errors . Wrap ( err , "failed to set permission on newly added channel" )
}
return nil
}
2017-12-11 11:01:24 +00:00
// Permissions
2017-12-15 01:39:18 +00:00
func ( d * Database ) GetPermission ( accountid int64 , channel string ) ( DBPermission , error ) {
2017-12-11 11:01:24 +00:00
dbp := DBPermission { }
// Return REGISTERED by default
2021-06-25 06:00:33 +00:00
dbp . Permission = permissionRegistered
2017-12-11 11:01:24 +00:00
err := d . db . Get ( & dbp , "SELECT * FROM permissions WHERE account=? AND channel=? LIMIT 1" , accountid , generateHash ( channel ) )
if p ( err ) {
return dbp , errors . Wrap ( err , "failed to fetch permission" )
2017-12-02 07:50:51 +00:00
}
2017-12-11 11:01:24 +00:00
return dbp , nil
2017-12-02 07:50:51 +00:00
}
2017-12-15 01:39:18 +00:00
func ( d * Database ) SetPermission ( accountid int64 , channel string , permission int ) error {
2017-12-02 07:50:51 +00:00
acc , err := d . Account ( accountid )
if err != nil {
2017-12-11 11:01:24 +00:00
log . Panicf ( "%+v" , err )
} else if acc . ID == 0 {
2017-12-02 07:50:51 +00:00
return nil
}
ch , err := d . Channel ( channel )
if err != nil {
return errors . Wrap ( err , "failed to fetch channel while setting permission" )
2017-12-11 11:01:24 +00:00
} else if ch . Channel == "" {
2017-12-02 07:50:51 +00:00
return nil
}
chh := generateHash ( channel )
2017-12-11 11:01:24 +00:00
dbp , err := d . GetPermission ( accountid , chh )
2017-12-02 07:50:51 +00:00
if err != nil {
return errors . Wrap ( err , "failed to set permission" )
}
2017-12-11 11:01:24 +00:00
if dbp . Channel != "" {
_ , err = d . db . Exec ( "UPDATE permissions SET permission=? WHERE account=? AND channel=?" , permission , accountid , chh )
2017-12-02 07:50:51 +00:00
if err != nil {
return errors . Wrap ( err , "failed to set permission" )
}
} else {
2017-12-11 11:01:24 +00:00
_ , err = d . db . Exec ( "INSERT INTO permissions (channel, account, permission) VALUES (?, ?, ?)" , chh , accountid , permission )
2017-12-02 07:50:51 +00:00
if err != nil {
return errors . Wrap ( err , "failed to set permission" )
}
}
return nil
}
2017-12-11 11:01:24 +00:00
// Bans
func ( d * Database ) Ban ( banid int ) ( DBBan , error ) {
b := DBBan { }
err := d . db . Get ( & b , "SELECT * FROM bans WHERE id=? LIMIT 1" , banid )
if p ( err ) {
return b , errors . Wrap ( err , "failed to fetch ban" )
}
return b , nil
}
func ( d * Database ) BanAddr ( addrhash string , channel string ) ( DBBan , error ) {
b := DBBan { }
2017-12-15 01:39:18 +00:00
if addrhash == "" {
return b , nil
}
err := d . db . Get ( & b , "SELECT * FROM bans WHERE channel=? AND `type`=? AND target=? AND (`expires` = 0 OR `expires` > ?)" , generateHash ( channel ) , BAN_TYPE_ADDRESS , addrhash , time . Now ( ) . Unix ( ) )
2017-12-11 11:01:24 +00:00
if p ( err ) {
return b , errors . Wrap ( err , "failed to fetch ban" )
}
return b , nil
}
2017-12-15 01:39:18 +00:00
func ( d * Database ) BanAccount ( accountid int64 , channel string ) ( DBBan , error ) {
2017-12-11 11:01:24 +00:00
b := DBBan { }
2017-12-15 01:39:18 +00:00
if accountid == 0 {
return b , nil
}
err := d . db . Get ( & b , "SELECT * FROM bans WHERE channel=? AND `type`=? AND target=? AND (`expires` = 0 OR `expires` > ?)" , generateHash ( channel ) , BAN_TYPE_ACCOUNT , accountid , time . Now ( ) . Unix ( ) )
2017-12-11 11:01:24 +00:00
if p ( err ) {
return b , errors . Wrap ( err , "failed to fetch ban" )
}
return b , nil
}
func ( d * Database ) AddBan ( b DBBan ) error {
2017-12-15 01:39:18 +00:00
_ , err := d . db . Exec ( "INSERT INTO bans (`channel`, `type`, `target`, `expires`, `reason`) VALUES (?, ?, ?, ?, ?)" , b . Channel , b . Type , b . Target , b . Expires , b . Reason )
2017-12-11 11:01:24 +00:00
if p ( err ) {
return errors . Wrap ( err , "failed to add ban" )
}
return nil
}