db: Remove SQLite db, hopefully looking up clients still works.Channel persistence is broken by this, will fix it later.

This commit is contained in:
Daniel Oaks 2016-09-17 21:23:04 +10:00
parent 969eed394f
commit ae69ef5cd6
7 changed files with 98 additions and 197 deletions

View File

@ -14,6 +14,7 @@ Initial release of Oragono!
### Added ### Added
* Added YAML config file format. * Added YAML config file format.
* Added buntdb key-value store for persistent data.
* Added native SSL/TLS support (thanks to @edmand). * Added native SSL/TLS support (thanks to @edmand).
* Added ability to generate testing certificates from the command line. * Added ability to generate testing certificates from the command line.
* Added support for looking up usernames with [ident](https://tools.ietf.org/html/rfc1413) on client connection. * Added support for looking up usernames with [ident](https://tools.ietf.org/html/rfc1413) on client connection.
@ -35,6 +36,7 @@ Initial release of Oragono!
### Removed ### Removed
* Removed gitconfig configuration format [replaced with YAML]. * Removed gitconfig configuration format [replaced with YAML].
* Removed sqlite database [replaced with buntdb key-value store].
* Removed `THEATER` command (it broke and I'm not that interested in putting the work in to get it working again with the aim of this project. PRs accepted). * Removed `THEATER` command (it broke and I'm not that interested in putting the work in to get it working again with the aim of this project. PRs accepted).
### Fixed ### Fixed

View File

@ -424,26 +424,31 @@ func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeO
} }
func (channel *Channel) Persist() (err error) { func (channel *Channel) Persist() (err error) {
if channel.flags[Persistent] {
//TODO(dan): Save topicSetBy/topicSetTime and createdTime
_, err = channel.server.db.Exec(`
INSERT OR REPLACE INTO channel
(name, flags, key, topic, user_limit, ban_list, except_list,
invite_list)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
channel.name.String(), channel.flags.String(), channel.key,
channel.topic, channel.userLimit, channel.lists[BanMask].String(),
channel.lists[ExceptMask].String(), channel.lists[InviteMask].String())
} else {
_, err = channel.server.db.Exec(`
DELETE FROM channel WHERE name = ?`, channel.name.String())
}
if err != nil {
Log.error.Println("Channel.Persist:", channel, err)
}
return return
//TODO(dan): Fix persistence
/*
if channel.flags[Persistent] {
//TODO(dan): Save topicSetBy/topicSetTime and createdTime
_, err = channel.server.db.Exec(`
INSERT OR REPLACE INTO channel
(name, flags, key, topic, user_limit, ban_list, except_list,
invite_list)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
channel.name.String(), channel.flags.String(), channel.key,
channel.topic, channel.userLimit, channel.lists[BanMask].String(),
channel.lists[ExceptMask].String(), channel.lists[InviteMask].String())
} else {
_, err = channel.server.db.Exec(`
DELETE FROM channel WHERE name = ?`, channel.name.String())
}
if err != nil {
Log.error.Println("Channel.Persist:", channel, err)
}
return
*/
} }
func (channel *Channel) Notice(client *Client, message string) { func (channel *Channel) Notice(client *Client, message string) {

View File

@ -4,33 +4,23 @@
package irc package irc
import ( import (
"database/sql"
"errors" "errors"
"log"
"regexp" "regexp"
"strings" "strings"
"github.com/DanielOaks/girc-go/ircmatch"
) )
var ( var (
ErrNickMissing = errors.New("nick missing") ErrNickMissing = errors.New("nick missing")
ErrNicknameInUse = errors.New("nickname in use") ErrNicknameInUse = errors.New("nickname in use")
ErrNicknameMismatch = errors.New("nickname mismatch") ErrNicknameMismatch = errors.New("nickname mismatch")
wildMaskExpr = regexp.MustCompile(`\*|\?`)
likeQuoter = strings.NewReplacer(
`\`, `\\`,
`%`, `\%`,
`_`, `\_`,
`*`, `%`,
`?`, `_`)
) )
func HasWildcards(mask string) bool {
return wildMaskExpr.MatchString(mask)
}
func ExpandUserHost(userhost Name) (expanded Name) { func ExpandUserHost(userhost Name) (expanded Name) {
expanded = userhost expanded = userhost
// fill in missing wildcards for nicks // fill in missing wildcards for nicks
//TODO(dan): this would fail with dan@lol, do we want to accommodate that?
if !strings.Contains(expanded.String(), "!") { if !strings.Contains(expanded.String(), "!") {
expanded += "!*" expanded += "!*"
} }
@ -40,19 +30,13 @@ func ExpandUserHost(userhost Name) (expanded Name) {
return return
} }
func QuoteLike(userhost Name) string {
return likeQuoter.Replace(userhost.String())
}
type ClientLookupSet struct { type ClientLookupSet struct {
byNick map[Name]*Client byNick map[Name]*Client
db *ClientDB
} }
func NewClientLookupSet() *ClientLookupSet { func NewClientLookupSet() *ClientLookupSet {
return &ClientLookupSet{ return &ClientLookupSet{
byNick: make(map[Name]*Client), byNick: make(map[Name]*Client),
db: NewClientDB(),
} }
} }
@ -68,7 +52,6 @@ func (clients *ClientLookupSet) Add(client *Client) error {
return ErrNicknameInUse return ErrNicknameInUse
} }
clients.byNick[client.Nick().ToLower()] = client clients.byNick[client.Nick().ToLower()] = client
clients.db.Add(client)
return nil return nil
} }
@ -80,101 +63,47 @@ func (clients *ClientLookupSet) Remove(client *Client) error {
return ErrNicknameMismatch return ErrNicknameMismatch
} }
delete(clients.byNick, client.nick.ToLower()) delete(clients.byNick, client.nick.ToLower())
clients.db.Remove(client)
return nil return nil
} }
func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) { func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
userhost = ExpandUserHost(userhost)
set = make(ClientSet) set = make(ClientSet)
rows, err := clients.db.db.Query(
`SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\'`, userhost = ExpandUserHost(userhost)
QuoteLike(userhost)) matcher := ircmatch.MakeMatch(userhost.String())
if err != nil {
Log.error.Println("ClientLookupSet.FindAll.Query:", err) var casemappedNickMask string
return for _, client := range clients.byNick {
} casemappedNickMask = NewName(client.nickMaskString).String()
for rows.Next() { if matcher.Match(casemappedNickMask) {
var sqlNickname string set.Add(client)
err := rows.Scan(&sqlNickname)
if err != nil {
Log.error.Println("ClientLookupSet.FindAll.Scan:", err)
return
} }
nickname := Name(sqlNickname)
client := clients.Get(nickname)
if client == nil {
Log.error.Println("ClientLookupSet.FindAll: missing client:", nickname)
continue
}
set.Add(client)
} }
return
return set
} }
func (clients *ClientLookupSet) Find(userhost Name) *Client { func (clients *ClientLookupSet) Find(userhost Name) *Client {
userhost = ExpandUserHost(userhost) userhost = ExpandUserHost(userhost)
row := clients.db.db.QueryRow( matcher := ircmatch.MakeMatch(userhost.String())
`SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\' LIMIT 1`,
QuoteLike(userhost))
var nickname Name
err := row.Scan(&nickname)
if err != nil {
Log.error.Println("ClientLookupSet.Find:", err)
return nil
}
return clients.Get(nickname)
}
// var casemappedNickMask string
// client db for _, client := range clients.byNick {
// casemappedNickMask = NewName(client.nickMaskString).String()
if matcher.Match(casemappedNickMask) {
type ClientDB struct { return client
db *sql.DB
}
func NewClientDB() *ClientDB {
db := &ClientDB{
db: OpenDB(":memory:"),
}
stmts := []string{
`CREATE TABLE client (
nickname TEXT NOT NULL COLLATE NOCASE UNIQUE,
userhost TEXT NOT NULL COLLATE NOCASE,
UNIQUE (nickname, userhost) ON CONFLICT REPLACE)`,
`CREATE UNIQUE INDEX idx_nick ON client (nickname COLLATE NOCASE)`,
`CREATE UNIQUE INDEX idx_uh ON client (userhost COLLATE NOCASE)`,
}
for _, stmt := range stmts {
_, err := db.db.Exec(stmt)
if err != nil {
log.Fatal("NewClientDB: ", stmt, err)
} }
} }
return db
}
func (db *ClientDB) Add(client *Client) { return nil
_, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`,
client.Nick().String(), client.UserHost().String())
if err != nil {
Log.error.Println("ClientDB.Add:", err)
}
}
func (db *ClientDB) Remove(client *Client) {
_, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`,
client.Nick().String())
if err != nil {
Log.error.Println("ClientDB.Remove:", err)
}
} }
// //
// usermask to regexp // usermask to regexp
// //
//TODO(dan): move this over to generally using glob syntax instead?
// kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
type UserMaskSet struct { type UserMaskSet struct {
masks map[Name]bool masks map[Name]bool
regexp *regexp.Regexp regexp *regexp.Regexp

View File

@ -84,8 +84,7 @@ type Config struct {
} }
Datastore struct { Datastore struct {
Path string Path string
SQLitePath string `yaml:"sqlite-path"`
} }
Registration struct { Registration struct {
@ -152,9 +151,6 @@ func LoadConfig(filename string) (config *Config, err error) {
if config.Datastore.Path == "" { if config.Datastore.Path == "" {
return nil, errors.New("Datastore path missing") return nil, errors.New("Datastore path missing")
} }
if config.Datastore.SQLitePath == "" {
return nil, errors.New("SQLite database path missing")
}
if len(config.Server.Listen) == 0 { if len(config.Server.Listen) == 0 {
return nil, errors.New("Server listening addresses missing") return nil, errors.New("Server listening addresses missing")
} }

View File

@ -4,79 +4,52 @@
package irc package irc
import ( import (
"database/sql"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"log" "log"
"os" "os"
_ "github.com/mattn/go-sqlite3"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
const ( const (
// 'version' of the database schema
keySchemaVersion = "db.version"
// key for the primary salt used by the ircd // key for the primary salt used by the ircd
keySalt = "crypto.salt" keySalt = "crypto.salt"
) )
func InitDB(buntpath string, path string) { // InitDB creates the database.
func InitDB(path string) {
// prepare kvstore db // prepare kvstore db
os.Remove(buntpath) //TODO(dan): fail if already exists instead? don't want to overwrite good data
store, err := buntdb.Open(buntpath) os.Remove(path)
store, err := buntdb.Open(path)
if err != nil { if err != nil {
log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error())) log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
} }
defer store.Close() defer store.Close()
err = store.Update(func(tx *buntdb.Tx) error { err = store.Update(func(tx *buntdb.Tx) error {
// set base db salt
salt, err := NewSalt() salt, err := NewSalt()
encodedSalt := base64.StdEncoding.EncodeToString(salt) encodedSalt := base64.StdEncoding.EncodeToString(salt)
if err != nil { if err != nil {
log.Fatal("Could not generate cryptographically-secure salt for the user:", err.Error()) log.Fatal("Could not generate cryptographically-secure salt for the user:", err.Error())
} }
tx.Set(keySalt, encodedSalt, nil) tx.Set(keySalt, encodedSalt, nil)
// set schema version
tx.Set(keySchemaVersion, "1", nil)
return nil return nil
}) })
if err != nil { if err != nil {
log.Fatal("Could not save bunt store:", err.Error()) log.Fatal("Could not save bunt store:", err.Error())
} }
// prepare SQLite db
os.Remove(path)
db := OpenDB(path)
defer db.Close()
_, err = db.Exec(`
CREATE TABLE channel (
name TEXT NOT NULL UNIQUE,
flags TEXT DEFAULT '',
key TEXT DEFAULT '',
topic TEXT DEFAULT '',
user_limit INTEGER DEFAULT 0,
ban_list TEXT DEFAULT '',
except_list TEXT DEFAULT '',
invite_list TEXT DEFAULT '')`)
if err != nil {
log.Fatal("initdb error: ", err)
}
} }
// UpgradeDB upgrades the datastore to the latest schema.
func UpgradeDB(path string) { func UpgradeDB(path string) {
db := OpenDB(path) return
alter := `ALTER TABLE channel ADD COLUMN %s TEXT DEFAULT ''`
cols := []string{"ban_list", "except_list", "invite_list"}
for _, col := range cols {
_, err := db.Exec(fmt.Sprintf(alter, col))
if err != nil {
log.Fatal("updatedb error: ", err)
}
}
}
func OpenDB(path string) *sql.DB {
db, err := sql.Open("sqlite3", path)
if err != nil {
log.Fatal("open db error: ", err)
}
return db
} }

View File

@ -8,7 +8,6 @@ package irc
import ( import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"database/sql"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"log" "log"
@ -39,7 +38,6 @@ type Server struct {
clients *ClientLookupSet clients *ClientLookupSet
commands chan Command commands chan Command
ctime time.Time ctime time.Time
db *sql.DB
store buntdb.DB store buntdb.DB
idle chan *Client idle chan *Client
limits Limits limits Limits
@ -79,7 +77,6 @@ func NewServer(config *Config) *Server {
clients: NewClientLookupSet(), clients: NewClientLookupSet(),
commands: make(chan Command), commands: make(chan Command),
ctime: time.Now(), ctime: time.Now(),
db: OpenDB(config.Datastore.SQLitePath),
idle: make(chan *Client), idle: make(chan *Client),
limits: Limits{ limits: Limits{
AwayLen: config.Limits.AwayLen, AwayLen: config.Limits.AwayLen,
@ -216,35 +213,38 @@ func loadChannelList(channel *Channel, list string, maskMode ChannelMode) {
} }
func (server *Server) loadChannels() { func (server *Server) loadChannels() {
rows, err := server.db.Query(` //TODO(dan): Fix channel persistence
SELECT name, flags, key, topic, user_limit, ban_list, except_list, /*
invite_list rows, err := server.db.Query(`
FROM channel`) SELECT name, flags, key, topic, user_limit, ban_list, except_list,
if err != nil { invite_list
log.Fatal("error loading channels: ", err) FROM channel`)
} if err != nil {
for rows.Next() { log.Fatal("error loading channels: ", err)
var name, flags, key, topic string }
var userLimit uint64 for rows.Next() {
var banList, exceptList, inviteList string var name, flags, key, topic string
err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList, var userLimit uint64
&exceptList, &inviteList) var banList, exceptList, inviteList string
if err != nil { err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList,
log.Println("Server.loadChannels:", err) &exceptList, &inviteList)
continue if err != nil {
} log.Println("Server.loadChannels:", err)
continue
}
channel := NewChannel(server, NewName(name), false) channel := NewChannel(server, NewName(name), false)
for _, flag := range flags { for _, flag := range flags {
channel.flags[ChannelMode(flag)] = true channel.flags[ChannelMode(flag)] = true
} }
channel.key = key channel.key = key
channel.topic = topic channel.topic = topic
channel.userLimit = userLimit channel.userLimit = userLimit
loadChannelList(channel, banList, BanMask) loadChannelList(channel, banList, BanMask)
loadChannelList(channel, exceptList, ExceptMask) loadChannelList(channel, exceptList, ExceptMask)
loadChannelList(channel, inviteList, InviteMask) loadChannelList(channel, inviteList, InviteMask)
} }
*/
} }
func (server *Server) Shutdown() { func (server *Server) Shutdown() {
@ -253,9 +253,6 @@ func (server *Server) Shutdown() {
client.Notice("Server is shutting down") client.Notice("Server is shutting down")
} }
if err := server.db.Close(); err != nil {
Log.error.Println("Server.Shutdown db.Close: error:", err)
}
if err := server.store.Close(); err != nil { if err := server.store.Close(); err != nil {
Log.error.Println("Server.Shutdown store.Close: error:", err) Log.error.Println("Server.Shutdown store.Close: error:", err)
} }
@ -263,7 +260,6 @@ func (server *Server) Shutdown() {
func (server *Server) Run() { func (server *Server) Run() {
// defer closing db/store // defer closing db/store
defer server.db.Close()
defer server.store.Close() defer server.store.Close()
done := false done := false

View File

@ -54,11 +54,11 @@ Options:
fmt.Print("\n") fmt.Print("\n")
fmt.Println(encoded) fmt.Println(encoded)
} else if arguments["initdb"].(bool) { } else if arguments["initdb"].(bool) {
irc.InitDB(config.Datastore.Path, config.Datastore.SQLitePath) irc.InitDB(config.Datastore.Path)
log.Println("databases initialized: ", config.Datastore.Path, config.Datastore.SQLitePath) log.Println("database initialized: ", config.Datastore.Path)
} else if arguments["upgradedb"].(bool) { } else if arguments["upgradedb"].(bool) {
irc.UpgradeDB(config.Datastore.SQLitePath) irc.UpgradeDB(config.Datastore.Path)
log.Println("database upgraded: ", config.Datastore.SQLitePath) log.Println("database upgraded: ", config.Datastore.Path)
} else if arguments["mkcerts"].(bool) { } else if arguments["mkcerts"].(bool) {
log.Println("making self-signed certificates") log.Println("making self-signed certificates")