123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116 |
- // Copyright (c) 2012-2014 Jeremy Latt
- // Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
- // released under the MIT license
-
- package irc
-
- import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "log"
- "os"
- "strconv"
- "strings"
- "time"
-
- "github.com/oragono/oragono/irc/modes"
- "github.com/oragono/oragono/irc/utils"
-
- "github.com/tidwall/buntdb"
- )
-
- const (
- // 'version' of the database schema
- keySchemaVersion = "db.version"
- // latest schema of the db
- latestDbSchema = 20
-
- keyCloakSecret = "crypto.cloak_secret"
- )
-
- type SchemaChanger func(*Config, *buntdb.Tx) error
-
- type SchemaChange struct {
- InitialVersion int // the change will take this version
- TargetVersion int // and transform it into this version
- Changer SchemaChanger
- }
-
- func checkDBReadyForInit(path string) error {
- _, err := os.Stat(path)
- if err == nil {
- return fmt.Errorf("Datastore already exists (delete it manually to continue): %s", path)
- } else if !os.IsNotExist(err) {
- return fmt.Errorf("Datastore path %s is inaccessible: %w", path, err)
- }
- return nil
- }
-
- // InitDB creates the database, implementing the `oragono initdb` command.
- func InitDB(path string) error {
- if err := checkDBReadyForInit(path); err != nil {
- return err
- }
-
- if err := initializeDB(path); err != nil {
- return fmt.Errorf("Could not save datastore: %w", err)
- }
- return nil
- }
-
- // internal database initialization code
- func initializeDB(path string) error {
- store, err := buntdb.Open(path)
- if err != nil {
- return err
- }
- defer store.Close()
-
- err = store.Update(func(tx *buntdb.Tx) error {
- // set schema version
- tx.Set(keySchemaVersion, strconv.Itoa(latestDbSchema), nil)
- tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
- return nil
- })
-
- return err
- }
-
- // OpenDatabase returns an existing database, performing a schema version check.
- func OpenDatabase(config *Config) (*buntdb.DB, error) {
- return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
- }
-
- // open the database, giving it at most one chance to auto-upgrade the schema
- func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
- db, err = buntdb.Open(config.Datastore.Path)
- if err != nil {
- return
- }
-
- defer func() {
- if err != nil && db != nil {
- db.Close()
- db = nil
- }
- }()
-
- // read the current version string
- var version int
- err = db.View(func(tx *buntdb.Tx) (err error) {
- vStr, err := tx.Get(keySchemaVersion)
- if err == nil {
- version, err = strconv.Atoi(vStr)
- }
- return err
- })
- if err != nil {
- return
- }
-
- if version == latestDbSchema {
- // success
- return
- }
-
- // XXX quiesce the DB so we can be sure it's safe to make a backup copy
- db.Close()
- db = nil
- if allowAutoupgrade {
- err = performAutoUpgrade(version, config)
- if err != nil {
- return
- }
- // successful autoupgrade, let's try this again:
- return openDatabaseInternal(config, false)
- } else {
- err = &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
- return
- }
- }
-
- func performAutoUpgrade(currentVersion int, config *Config) (err error) {
- path := config.Datastore.Path
- log.Printf("attempting to auto-upgrade schema from version %d to %d\n", currentVersion, latestDbSchema)
- timestamp := time.Now().UTC().Format("2006-01-02-15:04:05.000Z")
- backupPath := fmt.Sprintf("%s.v%d.%s.bak", path, currentVersion, timestamp)
- log.Printf("making a backup of current database at %s\n", backupPath)
- err = utils.CopyFile(path, backupPath)
- if err != nil {
- return err
- }
-
- err = UpgradeDB(config)
- if err != nil {
- // database upgrade is a single transaction, so we don't need to restore the backup;
- // we can just delete it
- os.Remove(backupPath)
- }
- return err
- }
-
- // UpgradeDB upgrades the datastore to the latest schema.
- func UpgradeDB(config *Config) (err error) {
- // #715: test that the database exists
- _, err = os.Stat(config.Datastore.Path)
- if err != nil {
- return err
- }
-
- store, err := buntdb.Open(config.Datastore.Path)
- if err != nil {
- return err
- }
- defer store.Close()
-
- var version int
- err = store.Update(func(tx *buntdb.Tx) error {
- for {
- vStr, _ := tx.Get(keySchemaVersion)
- version, _ = strconv.Atoi(vStr)
- if version == latestDbSchema {
- // success!
- break
- }
- change, ok := getSchemaChange(version)
- if !ok {
- // unable to upgrade to the desired version, roll back
- return &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
- }
- log.Printf("attempting to update schema from version %d\n", version)
- err := change.Changer(config, tx)
- if err != nil {
- return err
- }
- _, _, err = tx.Set(keySchemaVersion, strconv.Itoa(change.TargetVersion), nil)
- if err != nil {
- return err
- }
- log.Printf("successfully updated schema to version %d\n", change.TargetVersion)
- }
- return nil
- })
-
- if err != nil {
- log.Printf("database upgrade failed and was rolled back: %v\n", err)
- }
- return err
- }
-
- func LoadCloakSecret(db *buntdb.DB) (result string) {
- db.View(func(tx *buntdb.Tx) error {
- result, _ = tx.Get(keyCloakSecret)
- return nil
- })
- return
- }
-
- func StoreCloakSecret(db *buntdb.DB, secret string) {
- db.Update(func(tx *buntdb.Tx) error {
- tx.Set(keyCloakSecret, secret, nil)
- return nil
- })
- }
-
- func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
- // == version 1 -> 2 ==
- // account key changes and account.verified key bugfix.
-
- var keysToRemove []string
- newKeys := make(map[string]string)
-
- tx.AscendKeys("account *", func(key, value string) bool {
- keysToRemove = append(keysToRemove, key)
- splitkey := strings.Split(key, " ")
-
- // work around bug
- if splitkey[2] == "exists" {
- // manually create new verified key
- newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
- newKeys[newVerifiedKey] = "1"
- } else if splitkey[1] == "%s" {
- return true
- }
-
- newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
- newKeys[newKey] = value
-
- return true
- })
-
- for _, key := range keysToRemove {
- tx.Delete(key)
- }
- for key, value := range newKeys {
- tx.Set(key, value, nil)
- }
-
- return nil
- }
-
- // 1. channel founder names should be casefolded
- // 2. founder should be explicitly granted the ChannelFounder user mode
- // 3. explicitly initialize stored channel modes to the server default values
- func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
- var channels []string
- prefix := "channel.exists "
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- chname := strings.TrimPrefix(key, prefix)
- channels = append(channels, chname)
- return true
- })
-
- // founder names should be casefolded
- // founder should be explicitly granted the ChannelFounder user mode
- for _, channel := range channels {
- founderKey := "channel.founder " + channel
- founder, _ := tx.Get(founderKey)
- if founder != "" {
- founder, err := CasefoldName(founder)
- if err == nil {
- tx.Set(founderKey, founder, nil)
- accountToUmode := map[string]modes.Mode{
- founder: modes.ChannelFounder,
- }
- atustr, _ := json.Marshal(accountToUmode)
- tx.Set("channel.accounttoumode "+channel, string(atustr), nil)
- }
- }
- }
-
- // explicitly store the channel modes
- defaultModes := config.Channels.defaultModes
- modeStrings := make([]string, len(defaultModes))
- for i, mode := range defaultModes {
- modeStrings[i] = string(mode)
- }
- defaultModeString := strings.Join(modeStrings, "")
- for _, channel := range channels {
- tx.Set("channel.modes "+channel, defaultModeString, nil)
- }
-
- return nil
- }
-
- // 1. ban info format changed (from `legacyBanInfo` below to `IPBanInfo`)
- // 2. dlines against individual IPs are normalized into dlines against the appropriate /128 network
- func schemaChangeV3ToV4(config *Config, tx *buntdb.Tx) error {
- type ipRestrictTime struct {
- Duration time.Duration
- Expires time.Time
- }
- type legacyBanInfo struct {
- Reason string `json:"reason"`
- OperReason string `json:"oper_reason"`
- OperName string `json:"oper_name"`
- Time *ipRestrictTime `json:"time"`
- }
-
- now := time.Now()
- legacyToNewInfo := func(old legacyBanInfo) (new_ IPBanInfo) {
- new_.Reason = old.Reason
- new_.OperReason = old.OperReason
- new_.OperName = old.OperName
-
- if old.Time == nil {
- new_.TimeCreated = now
- new_.Duration = 0
- } else {
- new_.TimeCreated = old.Time.Expires.Add(-1 * old.Time.Duration)
- new_.Duration = old.Time.Duration
- }
- return
- }
-
- var keysToDelete []string
-
- prefix := "bans.dline "
- dlines := make(map[string]IPBanInfo)
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- keysToDelete = append(keysToDelete, key)
-
- var lbinfo legacyBanInfo
- id := strings.TrimPrefix(key, prefix)
- err := json.Unmarshal([]byte(value), &lbinfo)
- if err != nil {
- log.Printf("error unmarshaling legacy dline: %v\n", err)
- return true
- }
- // legacy keys can be either an IP or a CIDR
- hostNet, err := utils.NormalizedNetFromString(id)
- if err != nil {
- log.Printf("error unmarshaling legacy dline network: %v\n", err)
- return true
- }
- dlines[utils.NetToNormalizedString(hostNet)] = legacyToNewInfo(lbinfo)
-
- return true
- })
-
- setOptions := func(info IPBanInfo) *buntdb.SetOptions {
- if info.Duration == 0 {
- return nil
- }
- ttl := info.TimeCreated.Add(info.Duration).Sub(now)
- return &buntdb.SetOptions{Expires: true, TTL: ttl}
- }
-
- // store the new dlines
- for id, info := range dlines {
- b, err := json.Marshal(info)
- if err != nil {
- log.Printf("error marshaling migrated dline: %v\n", err)
- continue
- }
- tx.Set(fmt.Sprintf("bans.dlinev2 %s", id), string(b), setOptions(info))
- }
-
- // same operations against klines
- prefix = "bans.kline "
- klines := make(map[string]IPBanInfo)
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- keysToDelete = append(keysToDelete, key)
- mask := strings.TrimPrefix(key, prefix)
- var lbinfo legacyBanInfo
- err := json.Unmarshal([]byte(value), &lbinfo)
- if err != nil {
- log.Printf("error unmarshaling legacy kline: %v\n", err)
- return true
- }
- klines[mask] = legacyToNewInfo(lbinfo)
- return true
- })
-
- for mask, info := range klines {
- b, err := json.Marshal(info)
- if err != nil {
- log.Printf("error marshaling migrated kline: %v\n", err)
- continue
- }
- tx.Set(fmt.Sprintf("bans.klinev2 %s", mask), string(b), setOptions(info))
- }
-
- // clean up all the old entries
- for _, key := range keysToDelete {
- tx.Delete(key)
- }
-
- return nil
- }
-
- // create new key tracking channels that belong to an account
- func schemaChangeV4ToV5(config *Config, tx *buntdb.Tx) error {
- founderToChannels := make(map[string][]string)
- prefix := "channel.founder "
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- channel := strings.TrimPrefix(key, prefix)
- founderToChannels[value] = append(founderToChannels[value], channel)
- return true
- })
-
- for founder, channels := range founderToChannels {
- tx.Set(fmt.Sprintf("account.channels %s", founder), strings.Join(channels, ","), nil)
- }
- return nil
- }
-
- // custom nick enforcement was a separate db key, now it's part of settings
- func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
- accountToEnforcement := make(map[string]NickEnforcementMethod)
- prefix := "account.customenforcement "
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- account := strings.TrimPrefix(key, prefix)
- method, err := nickReservationFromString(value)
- if err == nil {
- accountToEnforcement[account] = method
- } else {
- log.Printf("skipping corrupt custom enforcement value for %s\n", account)
- }
- return true
- })
-
- for account, method := range accountToEnforcement {
- var settings AccountSettings
- settings.NickEnforcement = method
- text, err := json.Marshal(settings)
- if err != nil {
- return err
- }
- tx.Delete(prefix + account)
- tx.Set(fmt.Sprintf("account.settings %s", account), string(text), nil)
- }
- return nil
- }
-
- type maskInfoV7 struct {
- TimeCreated time.Time
- CreatorNickmask string
- CreatorAccount string
- }
-
- func schemaChangeV6ToV7(config *Config, tx *buntdb.Tx) error {
- now := time.Now().UTC()
- var channels []string
- prefix := "channel.exists "
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- channels = append(channels, strings.TrimPrefix(key, prefix))
- return true
- })
-
- converter := func(key string) {
- oldRawValue, err := tx.Get(key)
- if err != nil {
- return
- }
- var masks []string
- err = json.Unmarshal([]byte(oldRawValue), &masks)
- if err != nil {
- return
- }
- newCookedValue := make(map[string]maskInfoV7)
- for _, mask := range masks {
- normalizedMask, err := CanonicalizeMaskWildcard(mask)
- if err != nil {
- continue
- }
- newCookedValue[normalizedMask] = maskInfoV7{
- TimeCreated: now,
- CreatorNickmask: "*",
- CreatorAccount: "*",
- }
- }
- newRawValue, err := json.Marshal(newCookedValue)
- if err != nil {
- return
- }
- tx.Set(key, string(newRawValue), nil)
- }
-
- prefixes := []string{
- "channel.banlist %s",
- "channel.exceptlist %s",
- "channel.invitelist %s",
- }
- for _, channel := range channels {
- for _, prefix := range prefixes {
- converter(fmt.Sprintf(prefix, channel))
- }
- }
- return nil
- }
-
- type accountSettingsLegacyV7 struct {
- AutoreplayLines *int
- NickEnforcement NickEnforcementMethod
- AllowBouncer MulticlientAllowedSetting
- AutoreplayJoins bool
- }
-
- type accountSettingsLegacyV8 struct {
- AutoreplayLines *int
- NickEnforcement NickEnforcementMethod
- AllowBouncer MulticlientAllowedSetting
- ReplayJoins ReplayJoinsSetting
- }
-
- // #616: change autoreplay-joins to replay-joins
- func schemaChangeV7ToV8(config *Config, tx *buntdb.Tx) error {
- prefix := "account.settings "
- var accounts, blobs []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- var legacy accountSettingsLegacyV7
- var current accountSettingsLegacyV8
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- account := strings.TrimPrefix(key, prefix)
- err := json.Unmarshal([]byte(value), &legacy)
- if err != nil {
- log.Printf("corrupt record for %s: %v\n", account, err)
- return true
- }
- current.AutoreplayLines = legacy.AutoreplayLines
- current.NickEnforcement = legacy.NickEnforcement
- current.AllowBouncer = legacy.AllowBouncer
- if legacy.AutoreplayJoins {
- current.ReplayJoins = ReplayJoinsAlways
- } else {
- current.ReplayJoins = ReplayJoinsCommandsOnly
- }
- blob, err := json.Marshal(current)
- if err != nil {
- log.Printf("could not marshal record for %s: %v\n", account, err)
- return true
- }
- accounts = append(accounts, account)
- blobs = append(blobs, string(blob))
- return true
- })
- for i, account := range accounts {
- tx.Set(prefix+account, blobs[i], nil)
- }
- return nil
- }
-
- type accountCredsLegacyV8 struct {
- Version uint
- PassphraseSalt []byte // legacy field, not used by v1 and later
- PassphraseHash []byte
- Certificate string
- }
-
- type accountCredsLegacyV9 struct {
- Version uint
- PassphraseSalt []byte // legacy field, not used by v1 and later
- PassphraseHash []byte
- Certfps []string
- }
-
- // #530: support multiple client certificate fingerprints
- func schemaChangeV8ToV9(config *Config, tx *buntdb.Tx) error {
- prefix := "account.credentials "
- var accounts, blobs []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- var legacy accountCredsLegacyV8
- var current accountCredsLegacyV9
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- account := strings.TrimPrefix(key, prefix)
- err := json.Unmarshal([]byte(value), &legacy)
- if err != nil {
- log.Printf("corrupt record for %s: %v\n", account, err)
- return true
- }
- current.Version = legacy.Version
- current.PassphraseSalt = legacy.PassphraseSalt // ugh can't get rid of this
- current.PassphraseHash = legacy.PassphraseHash
- if legacy.Certificate != "" {
- current.Certfps = []string{legacy.Certificate}
- }
- blob, err := json.Marshal(current)
- if err != nil {
- log.Printf("could not marshal record for %s: %v\n", account, err)
- return true
- }
- accounts = append(accounts, account)
- blobs = append(blobs, string(blob))
- return true
- })
- for i, account := range accounts {
- tx.Set(prefix+account, blobs[i], nil)
- }
- return nil
- }
-
- // #836: account registration time at nanosecond resolution
- // (mostly to simplify testing)
- func schemaChangeV9ToV10(config *Config, tx *buntdb.Tx) error {
- prefix := "account.registered.time "
- var accounts, times []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- account := strings.TrimPrefix(key, prefix)
- accounts = append(accounts, account)
- times = append(times, value)
- return true
- })
- for i, account := range accounts {
- time, err := strconv.ParseInt(times[i], 10, 64)
- if err != nil {
- log.Printf("corrupt registration time entry for %s: %v\n", account, err)
- continue
- }
- time = time * 1000000000
- tx.Set(prefix+account, strconv.FormatInt(time, 10), nil)
- }
- return nil
- }
-
- // #952: move the cloak secret into the database,
- // generate a new one if necessary
- func schemaChangeV10ToV11(config *Config, tx *buntdb.Tx) error {
- cloakSecret := config.Server.Cloaks.LegacySecretValue
- if cloakSecret == "" || cloakSecret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
- cloakSecret = utils.GenerateSecretKey()
- }
- _, _, err := tx.Set(keyCloakSecret, cloakSecret, nil)
- return err
- }
-
- // #1027: NickEnforcementTimeout (2) was removed,
- // NickEnforcementStrict was 3 and is now 2
- func schemaChangeV11ToV12(config *Config, tx *buntdb.Tx) error {
- prefix := "account.settings "
- var accounts, rawSettings []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- account := strings.TrimPrefix(key, prefix)
- accounts = append(accounts, account)
- rawSettings = append(rawSettings, value)
- return true
- })
-
- for i, account := range accounts {
- var settings AccountSettings
- err := json.Unmarshal([]byte(rawSettings[i]), &settings)
- if err != nil {
- log.Printf("corrupt account settings entry for %s: %v\n", account, err)
- continue
- }
- // upgrade NickEnforcementTimeout (which was 2) to NickEnforcementStrict (currently 2),
- // fix up the old value of NickEnforcementStrict (3) to the current value (2)
- if int(settings.NickEnforcement) == 3 {
- settings.NickEnforcement = NickEnforcementMethod(2)
- text, err := json.Marshal(settings)
- if err != nil {
- return err
- }
- tx.Set(prefix+account, string(text), nil)
- }
- }
- return nil
- }
-
- type accountCredsLegacyV13 struct {
- Version CredentialsVersion
- PassphraseHash []byte
- Certfps []string
- }
-
- // see #212 / #284. this packs the legacy salts into a single passphrase hash,
- // allowing legacy passphrases to be verified using the new API `checkLegacyPassphrase`.
- func schemaChangeV12ToV13(config *Config, tx *buntdb.Tx) error {
- salt, err := tx.Get("crypto.salt")
- if err != nil {
- return nil // no change required
- }
- tx.Delete("crypto.salt")
- rawSalt, err := base64.StdEncoding.DecodeString(salt)
- if err != nil {
- return nil // just throw away the creds at this point
- }
- prefix := "account.credentials "
- var accounts []string
- var credentials []accountCredsLegacyV13
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- account := strings.TrimPrefix(key, prefix)
-
- var credsOld accountCredsLegacyV9
- err = json.Unmarshal([]byte(value), &credsOld)
- if err != nil {
- return true
- }
- // skip if these aren't legacy creds!
- if credsOld.Version != 0 {
- return true
- }
-
- var credsNew accountCredsLegacyV13
- credsNew.Version = 0 // mark hash for migration
- credsNew.Certfps = credsOld.Certfps
- credsNew.PassphraseHash = append(credsNew.PassphraseHash, rawSalt...)
- credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseSalt...)
- credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseHash...)
-
- accounts = append(accounts, account)
- credentials = append(credentials, credsNew)
- return true
- })
-
- for i, account := range accounts {
- bytesOut, err := json.Marshal(credentials[i])
- if err != nil {
- return err
- }
- _, _, err = tx.Set(prefix+account, string(bytesOut), nil)
- if err != nil {
- return err
- }
- }
-
- return nil
- }
-
- // channel registration time and topic set time at nanosecond resolution
- func schemaChangeV13ToV14(config *Config, tx *buntdb.Tx) error {
- prefix := "channel.registered.time "
- var channels, times []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- channel := strings.TrimPrefix(key, prefix)
- channels = append(channels, channel)
- times = append(times, value)
- return true
- })
-
- billion := int64(time.Second)
- for i, channel := range channels {
- regTime, err := strconv.ParseInt(times[i], 10, 64)
- if err != nil {
- log.Printf("corrupt registration time entry for %s: %v\n", channel, err)
- continue
- }
- regTime = regTime * billion
- tx.Set(prefix+channel, strconv.FormatInt(regTime, 10), nil)
-
- topicTimeKey := "channel.topic.settime " + channel
- topicSetAt, err := tx.Get(topicTimeKey)
- if err == nil {
- if setTime, err := strconv.ParseInt(topicSetAt, 10, 64); err == nil {
- tx.Set(topicTimeKey, strconv.FormatInt(setTime*billion, 10), nil)
- }
- }
- }
- return nil
- }
-
- // #1327: delete any invalid klines
- func schemaChangeV14ToV15(config *Config, tx *buntdb.Tx) error {
- prefix := "bans.klinev2 "
- var keys []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- if key != strings.TrimSpace(key) {
- keys = append(keys, key)
- }
- return true
- })
- // don't bother trying to fix these up
- for _, key := range keys {
- tx.Delete(key)
- }
- return nil
- }
-
- // #1330: delete any stale realname records
- func schemaChangeV15ToV16(config *Config, tx *buntdb.Tx) error {
- prefix := "account.realname "
- verifiedPrefix := "account.verified "
- var keys []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- acct := strings.TrimPrefix(key, prefix)
- verifiedKey := verifiedPrefix + acct
- _, verifiedErr := tx.Get(verifiedKey)
- if verifiedErr != nil {
- keys = append(keys, key)
- }
- return true
- })
- for _, key := range keys {
- tx.Delete(key)
- }
- return nil
- }
-
- // #1346: remove vhost request queue
- func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
- prefix := "vhostQueue "
- var keys []string
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- keys = append(keys, key)
- return true
- })
-
- for _, key := range keys {
- tx.Delete(key)
- }
- return nil
- }
-
- // #1274: we used to suspend accounts by deleting their "verified" key,
- // now we save some metadata under a new key
- func schemaChangeV17ToV18(config *Config, tx *buntdb.Tx) error {
- now := time.Now().UTC()
-
- exists := "account.exists "
- suspended := "account.suspended "
- verif := "account.verified "
- verifCode := "account.verificationcode "
-
- var accounts []string
-
- tx.AscendGreaterOrEqual("", exists, func(key, value string) bool {
- if !strings.HasPrefix(key, exists) {
- return false
- }
- account := strings.TrimPrefix(key, exists)
- _, verifiedErr := tx.Get(verif + account)
- _, verifCodeErr := tx.Get(verifCode + account)
- if verifiedErr != nil && verifCodeErr != nil {
- // verified key not present, but there's no code either,
- // this is a suspension
- accounts = append(accounts, account)
- }
- return true
- })
-
- type accountSuspensionV18 struct {
- TimeCreated time.Time
- Duration time.Duration
- OperName string
- Reason string
- }
-
- for _, account := range accounts {
- var sus accountSuspensionV18
- sus.TimeCreated = now
- sus.OperName = "*"
- sus.Reason = "[unknown]"
- susBytes, err := json.Marshal(sus)
- if err != nil {
- return err
- }
- tx.Set(suspended+account, string(susBytes), nil)
- }
-
- return nil
- }
-
- // #1345: persist the channel-user modes of always-on clients
- func schemaChangeV18To19(config *Config, tx *buntdb.Tx) error {
- channelToAmodesCache := make(map[string]map[string]modes.Mode)
- joinedto := "account.joinedto "
- var accounts []string
- var channels [][]string
- tx.AscendGreaterOrEqual("", joinedto, func(key, value string) bool {
- if !strings.HasPrefix(key, joinedto) {
- return false
- }
- accounts = append(accounts, strings.TrimPrefix(key, joinedto))
- var ch []string
- if value != "" {
- ch = strings.Split(value, ",")
- }
- channels = append(channels, ch)
- return true
- })
-
- for i := 0; i < len(accounts); i++ {
- account := accounts[i]
- channels := channels[i]
- tx.Delete(joinedto + account)
- newValue := make(map[string]string, len(channels))
- for _, channel := range channels {
- chcfname, err := CasefoldChannel(channel)
- if err != nil {
- continue
- }
- // get amodes from the channelToAmodesCache, fill if necessary
- amodes, ok := channelToAmodesCache[chcfname]
- if !ok {
- amodeStr, _ := tx.Get("channel.accounttoumode " + chcfname)
- if amodeStr != "" {
- jErr := json.Unmarshal([]byte(amodeStr), &amodes)
- if jErr != nil {
- log.Printf("error retrieving amodes for %s: %v\n", channel, jErr)
- amodes = nil
- }
- }
- // setting/using the nil value here is ok
- channelToAmodesCache[chcfname] = amodes
- }
- if mode, ok := amodes[account]; ok {
- newValue[channel] = string(mode)
- } else {
- newValue[channel] = ""
- }
- }
- newValueBytes, jErr := json.Marshal(newValue)
- if jErr != nil {
- log.Printf("couldn't serialize new mode values for v19: %v\n", jErr)
- continue
- }
- tx.Set("account.channeltomodes "+account, string(newValueBytes), nil)
- }
-
- return nil
- }
-
- // #1490: start tracking join times for always-on clients
- func schemaChangeV19To20(config *Config, tx *buntdb.Tx) error {
- type joinData struct {
- Modes string
- JoinTime int64
- }
-
- var accounts []string
- var data []string
-
- now := time.Now().UnixNano()
-
- prefix := "account.channeltomodes "
- tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
- if !strings.HasPrefix(key, prefix) {
- return false
- }
- accounts = append(accounts, strings.TrimPrefix(key, prefix))
- data = append(data, value)
- return true
- })
-
- for i, account := range accounts {
- var existingMap map[string]string
- err := json.Unmarshal([]byte(data[i]), &existingMap)
- if err != nil {
- return err
- }
- newMap := make(map[string]joinData)
- for channel, modeStr := range existingMap {
- newMap[channel] = joinData{
- Modes: modeStr,
- JoinTime: now,
- }
- }
- serialized, err := json.Marshal(newMap)
- if err != nil {
- return err
- }
- tx.Set(prefix+account, string(serialized), nil)
- }
-
- return nil
- }
-
- func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
- for _, change := range allChanges {
- if initialVersion == change.InitialVersion {
- return change, true
- }
- }
- return
- }
-
- var allChanges = []SchemaChange{
- {
- InitialVersion: 1,
- TargetVersion: 2,
- Changer: schemaChangeV1toV2,
- },
- {
- InitialVersion: 2,
- TargetVersion: 3,
- Changer: schemaChangeV2ToV3,
- },
- {
- InitialVersion: 3,
- TargetVersion: 4,
- Changer: schemaChangeV3ToV4,
- },
- {
- InitialVersion: 4,
- TargetVersion: 5,
- Changer: schemaChangeV4ToV5,
- },
- {
- InitialVersion: 5,
- TargetVersion: 6,
- Changer: schemaChangeV5ToV6,
- },
- {
- InitialVersion: 6,
- TargetVersion: 7,
- Changer: schemaChangeV6ToV7,
- },
- {
- InitialVersion: 7,
- TargetVersion: 8,
- Changer: schemaChangeV7ToV8,
- },
- {
- InitialVersion: 8,
- TargetVersion: 9,
- Changer: schemaChangeV8ToV9,
- },
- {
- InitialVersion: 9,
- TargetVersion: 10,
- Changer: schemaChangeV9ToV10,
- },
- {
- InitialVersion: 10,
- TargetVersion: 11,
- Changer: schemaChangeV10ToV11,
- },
- {
- InitialVersion: 11,
- TargetVersion: 12,
- Changer: schemaChangeV11ToV12,
- },
- {
- InitialVersion: 12,
- TargetVersion: 13,
- Changer: schemaChangeV12ToV13,
- },
- {
- InitialVersion: 13,
- TargetVersion: 14,
- Changer: schemaChangeV13ToV14,
- },
- {
- InitialVersion: 14,
- TargetVersion: 15,
- Changer: schemaChangeV14ToV15,
- },
- {
- InitialVersion: 15,
- TargetVersion: 16,
- Changer: schemaChangeV15ToV16,
- },
- {
- InitialVersion: 16,
- TargetVersion: 17,
- Changer: schemaChangeV16ToV17,
- },
- {
- InitialVersion: 17,
- TargetVersion: 18,
- Changer: schemaChangeV17ToV18,
- },
- {
- InitialVersion: 18,
- TargetVersion: 19,
- Changer: schemaChangeV18To19,
- },
- {
- InitialVersion: 19,
- TargetVersion: 20,
- Changer: schemaChangeV19To20,
- },
- }
|