You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

database.go 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
  3. // released under the MIT license
  4. package irc
  5. import (
  6. "encoding/base64"
  7. "encoding/json"
  8. "fmt"
  9. "log"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/oragono/oragono/irc/modes"
  15. "github.com/oragono/oragono/irc/utils"
  16. "github.com/tidwall/buntdb"
  17. )
  18. const (
  19. // 'version' of the database schema
  20. keySchemaVersion = "db.version"
  21. // latest schema of the db
  22. latestDbSchema = "17"
  23. keyCloakSecret = "crypto.cloak_secret"
  24. )
  25. type SchemaChanger func(*Config, *buntdb.Tx) error
  26. type SchemaChange struct {
  27. InitialVersion string // the change will take this version
  28. TargetVersion string // and transform it into this version
  29. Changer SchemaChanger
  30. }
  31. // maps an initial version to a schema change capable of upgrading it
  32. var schemaChanges map[string]SchemaChange
  33. func checkDBReadyForInit(path string) error {
  34. _, err := os.Stat(path)
  35. if err == nil {
  36. return fmt.Errorf("Datastore already exists (delete it manually to continue): %s", path)
  37. } else if !os.IsNotExist(err) {
  38. return fmt.Errorf("Datastore path %s is inaccessible: %w", path, err)
  39. }
  40. return nil
  41. }
  42. // InitDB creates the database, implementing the `oragono initdb` command.
  43. func InitDB(path string) error {
  44. if err := checkDBReadyForInit(path); err != nil {
  45. return err
  46. }
  47. if err := initializeDB(path); err != nil {
  48. return fmt.Errorf("Could not save datastore: %w", err)
  49. }
  50. return nil
  51. }
  52. // internal database initialization code
  53. func initializeDB(path string) error {
  54. store, err := buntdb.Open(path)
  55. if err != nil {
  56. return err
  57. }
  58. defer store.Close()
  59. err = store.Update(func(tx *buntdb.Tx) error {
  60. // set schema version
  61. tx.Set(keySchemaVersion, latestDbSchema, nil)
  62. tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
  63. return nil
  64. })
  65. return err
  66. }
  67. // OpenDatabase returns an existing database, performing a schema version check.
  68. func OpenDatabase(config *Config) (*buntdb.DB, error) {
  69. return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
  70. }
  71. // open the database, giving it at most one chance to auto-upgrade the schema
  72. func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
  73. db, err = buntdb.Open(config.Datastore.Path)
  74. if err != nil {
  75. return
  76. }
  77. defer func() {
  78. if err != nil && db != nil {
  79. db.Close()
  80. db = nil
  81. }
  82. }()
  83. // read the current version string
  84. var version string
  85. err = db.View(func(tx *buntdb.Tx) error {
  86. version, err = tx.Get(keySchemaVersion)
  87. return err
  88. })
  89. if err != nil {
  90. return
  91. }
  92. if version == latestDbSchema {
  93. // success
  94. return
  95. }
  96. // XXX quiesce the DB so we can be sure it's safe to make a backup copy
  97. db.Close()
  98. db = nil
  99. if allowAutoupgrade {
  100. err = performAutoUpgrade(version, config)
  101. if err != nil {
  102. return
  103. }
  104. // successful autoupgrade, let's try this again:
  105. return openDatabaseInternal(config, false)
  106. } else {
  107. err = &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
  108. return
  109. }
  110. }
  111. func performAutoUpgrade(currentVersion string, config *Config) (err error) {
  112. path := config.Datastore.Path
  113. log.Printf("attempting to auto-upgrade schema from version %s to %s\n", currentVersion, latestDbSchema)
  114. timestamp := time.Now().UTC().Format("2006-01-02-15:04:05.000Z")
  115. backupPath := fmt.Sprintf("%s.v%s.%s.bak", path, currentVersion, timestamp)
  116. log.Printf("making a backup of current database at %s\n", backupPath)
  117. err = utils.CopyFile(path, backupPath)
  118. if err != nil {
  119. return err
  120. }
  121. err = UpgradeDB(config)
  122. if err != nil {
  123. // database upgrade is a single transaction, so we don't need to restore the backup;
  124. // we can just delete it
  125. os.Remove(backupPath)
  126. }
  127. return err
  128. }
  129. // UpgradeDB upgrades the datastore to the latest schema.
  130. func UpgradeDB(config *Config) (err error) {
  131. // #715: test that the database exists
  132. _, err = os.Stat(config.Datastore.Path)
  133. if err != nil {
  134. return err
  135. }
  136. store, err := buntdb.Open(config.Datastore.Path)
  137. if err != nil {
  138. return err
  139. }
  140. defer store.Close()
  141. var version string
  142. err = store.Update(func(tx *buntdb.Tx) error {
  143. for {
  144. version, _ = tx.Get(keySchemaVersion)
  145. change, schemaNeedsChange := schemaChanges[version]
  146. if !schemaNeedsChange {
  147. if version == latestDbSchema {
  148. // success!
  149. break
  150. }
  151. // unable to upgrade to the desired version, roll back
  152. return &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
  153. }
  154. log.Println("attempting to update schema from version " + version)
  155. err := change.Changer(config, tx)
  156. if err != nil {
  157. return err
  158. }
  159. _, _, err = tx.Set(keySchemaVersion, change.TargetVersion, nil)
  160. if err != nil {
  161. return err
  162. }
  163. log.Println("successfully updated schema to version " + change.TargetVersion)
  164. }
  165. return nil
  166. })
  167. if err != nil {
  168. log.Printf("database upgrade failed and was rolled back: %v\n", err)
  169. }
  170. return err
  171. }
  172. func LoadCloakSecret(db *buntdb.DB) (result string) {
  173. db.View(func(tx *buntdb.Tx) error {
  174. result, _ = tx.Get(keyCloakSecret)
  175. return nil
  176. })
  177. return
  178. }
  179. func StoreCloakSecret(db *buntdb.DB, secret string) {
  180. db.Update(func(tx *buntdb.Tx) error {
  181. tx.Set(keyCloakSecret, secret, nil)
  182. return nil
  183. })
  184. }
  185. func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
  186. // == version 1 -> 2 ==
  187. // account key changes and account.verified key bugfix.
  188. var keysToRemove []string
  189. newKeys := make(map[string]string)
  190. tx.AscendKeys("account *", func(key, value string) bool {
  191. keysToRemove = append(keysToRemove, key)
  192. splitkey := strings.Split(key, " ")
  193. // work around bug
  194. if splitkey[2] == "exists" {
  195. // manually create new verified key
  196. newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
  197. newKeys[newVerifiedKey] = "1"
  198. } else if splitkey[1] == "%s" {
  199. return true
  200. }
  201. newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
  202. newKeys[newKey] = value
  203. return true
  204. })
  205. for _, key := range keysToRemove {
  206. tx.Delete(key)
  207. }
  208. for key, value := range newKeys {
  209. tx.Set(key, value, nil)
  210. }
  211. return nil
  212. }
  213. // 1. channel founder names should be casefolded
  214. // 2. founder should be explicitly granted the ChannelFounder user mode
  215. // 3. explicitly initialize stored channel modes to the server default values
  216. func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
  217. var channels []string
  218. prefix := "channel.exists "
  219. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  220. if !strings.HasPrefix(key, prefix) {
  221. return false
  222. }
  223. chname := strings.TrimPrefix(key, prefix)
  224. channels = append(channels, chname)
  225. return true
  226. })
  227. // founder names should be casefolded
  228. // founder should be explicitly granted the ChannelFounder user mode
  229. for _, channel := range channels {
  230. founderKey := "channel.founder " + channel
  231. founder, _ := tx.Get(founderKey)
  232. if founder != "" {
  233. founder, err := CasefoldName(founder)
  234. if err == nil {
  235. tx.Set(founderKey, founder, nil)
  236. accountToUmode := map[string]modes.Mode{
  237. founder: modes.ChannelFounder,
  238. }
  239. atustr, _ := json.Marshal(accountToUmode)
  240. tx.Set("channel.accounttoumode "+channel, string(atustr), nil)
  241. }
  242. }
  243. }
  244. // explicitly store the channel modes
  245. defaultModes := config.Channels.defaultModes
  246. modeStrings := make([]string, len(defaultModes))
  247. for i, mode := range defaultModes {
  248. modeStrings[i] = string(mode)
  249. }
  250. defaultModeString := strings.Join(modeStrings, "")
  251. for _, channel := range channels {
  252. tx.Set("channel.modes "+channel, defaultModeString, nil)
  253. }
  254. return nil
  255. }
  256. // 1. ban info format changed (from `legacyBanInfo` below to `IPBanInfo`)
  257. // 2. dlines against individual IPs are normalized into dlines against the appropriate /128 network
  258. func schemaChangeV3ToV4(config *Config, tx *buntdb.Tx) error {
  259. type ipRestrictTime struct {
  260. Duration time.Duration
  261. Expires time.Time
  262. }
  263. type legacyBanInfo struct {
  264. Reason string `json:"reason"`
  265. OperReason string `json:"oper_reason"`
  266. OperName string `json:"oper_name"`
  267. Time *ipRestrictTime `json:"time"`
  268. }
  269. now := time.Now()
  270. legacyToNewInfo := func(old legacyBanInfo) (new_ IPBanInfo) {
  271. new_.Reason = old.Reason
  272. new_.OperReason = old.OperReason
  273. new_.OperName = old.OperName
  274. if old.Time == nil {
  275. new_.TimeCreated = now
  276. new_.Duration = 0
  277. } else {
  278. new_.TimeCreated = old.Time.Expires.Add(-1 * old.Time.Duration)
  279. new_.Duration = old.Time.Duration
  280. }
  281. return
  282. }
  283. var keysToDelete []string
  284. prefix := "bans.dline "
  285. dlines := make(map[string]IPBanInfo)
  286. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  287. if !strings.HasPrefix(key, prefix) {
  288. return false
  289. }
  290. keysToDelete = append(keysToDelete, key)
  291. var lbinfo legacyBanInfo
  292. id := strings.TrimPrefix(key, prefix)
  293. err := json.Unmarshal([]byte(value), &lbinfo)
  294. if err != nil {
  295. log.Printf("error unmarshaling legacy dline: %v\n", err)
  296. return true
  297. }
  298. // legacy keys can be either an IP or a CIDR
  299. hostNet, err := utils.NormalizedNetFromString(id)
  300. if err != nil {
  301. log.Printf("error unmarshaling legacy dline network: %v\n", err)
  302. return true
  303. }
  304. dlines[utils.NetToNormalizedString(hostNet)] = legacyToNewInfo(lbinfo)
  305. return true
  306. })
  307. setOptions := func(info IPBanInfo) *buntdb.SetOptions {
  308. if info.Duration == 0 {
  309. return nil
  310. }
  311. ttl := info.TimeCreated.Add(info.Duration).Sub(now)
  312. return &buntdb.SetOptions{Expires: true, TTL: ttl}
  313. }
  314. // store the new dlines
  315. for id, info := range dlines {
  316. b, err := json.Marshal(info)
  317. if err != nil {
  318. log.Printf("error marshaling migrated dline: %v\n", err)
  319. continue
  320. }
  321. tx.Set(fmt.Sprintf("bans.dlinev2 %s", id), string(b), setOptions(info))
  322. }
  323. // same operations against klines
  324. prefix = "bans.kline "
  325. klines := make(map[string]IPBanInfo)
  326. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  327. if !strings.HasPrefix(key, prefix) {
  328. return false
  329. }
  330. keysToDelete = append(keysToDelete, key)
  331. mask := strings.TrimPrefix(key, prefix)
  332. var lbinfo legacyBanInfo
  333. err := json.Unmarshal([]byte(value), &lbinfo)
  334. if err != nil {
  335. log.Printf("error unmarshaling legacy kline: %v\n", err)
  336. return true
  337. }
  338. klines[mask] = legacyToNewInfo(lbinfo)
  339. return true
  340. })
  341. for mask, info := range klines {
  342. b, err := json.Marshal(info)
  343. if err != nil {
  344. log.Printf("error marshaling migrated kline: %v\n", err)
  345. continue
  346. }
  347. tx.Set(fmt.Sprintf("bans.klinev2 %s", mask), string(b), setOptions(info))
  348. }
  349. // clean up all the old entries
  350. for _, key := range keysToDelete {
  351. tx.Delete(key)
  352. }
  353. return nil
  354. }
  355. // create new key tracking channels that belong to an account
  356. func schemaChangeV4ToV5(config *Config, tx *buntdb.Tx) error {
  357. founderToChannels := make(map[string][]string)
  358. prefix := "channel.founder "
  359. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  360. if !strings.HasPrefix(key, prefix) {
  361. return false
  362. }
  363. channel := strings.TrimPrefix(key, prefix)
  364. founderToChannels[value] = append(founderToChannels[value], channel)
  365. return true
  366. })
  367. for founder, channels := range founderToChannels {
  368. tx.Set(fmt.Sprintf("account.channels %s", founder), strings.Join(channels, ","), nil)
  369. }
  370. return nil
  371. }
  372. // custom nick enforcement was a separate db key, now it's part of settings
  373. func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
  374. accountToEnforcement := make(map[string]NickEnforcementMethod)
  375. prefix := "account.customenforcement "
  376. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  377. if !strings.HasPrefix(key, prefix) {
  378. return false
  379. }
  380. account := strings.TrimPrefix(key, prefix)
  381. method, err := nickReservationFromString(value)
  382. if err == nil {
  383. accountToEnforcement[account] = method
  384. } else {
  385. log.Printf("skipping corrupt custom enforcement value for %s\n", account)
  386. }
  387. return true
  388. })
  389. for account, method := range accountToEnforcement {
  390. var settings AccountSettings
  391. settings.NickEnforcement = method
  392. text, err := json.Marshal(settings)
  393. if err != nil {
  394. return err
  395. }
  396. tx.Delete(prefix + account)
  397. tx.Set(fmt.Sprintf("account.settings %s", account), string(text), nil)
  398. }
  399. return nil
  400. }
  401. type maskInfoV7 struct {
  402. TimeCreated time.Time
  403. CreatorNickmask string
  404. CreatorAccount string
  405. }
  406. func schemaChangeV6ToV7(config *Config, tx *buntdb.Tx) error {
  407. now := time.Now().UTC()
  408. var channels []string
  409. prefix := "channel.exists "
  410. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  411. if !strings.HasPrefix(key, prefix) {
  412. return false
  413. }
  414. channels = append(channels, strings.TrimPrefix(key, prefix))
  415. return true
  416. })
  417. converter := func(key string) {
  418. oldRawValue, err := tx.Get(key)
  419. if err != nil {
  420. return
  421. }
  422. var masks []string
  423. err = json.Unmarshal([]byte(oldRawValue), &masks)
  424. if err != nil {
  425. return
  426. }
  427. newCookedValue := make(map[string]maskInfoV7)
  428. for _, mask := range masks {
  429. normalizedMask, err := CanonicalizeMaskWildcard(mask)
  430. if err != nil {
  431. continue
  432. }
  433. newCookedValue[normalizedMask] = maskInfoV7{
  434. TimeCreated: now,
  435. CreatorNickmask: "*",
  436. CreatorAccount: "*",
  437. }
  438. }
  439. newRawValue, err := json.Marshal(newCookedValue)
  440. if err != nil {
  441. return
  442. }
  443. tx.Set(key, string(newRawValue), nil)
  444. }
  445. prefixes := []string{
  446. "channel.banlist %s",
  447. "channel.exceptlist %s",
  448. "channel.invitelist %s",
  449. }
  450. for _, channel := range channels {
  451. for _, prefix := range prefixes {
  452. converter(fmt.Sprintf(prefix, channel))
  453. }
  454. }
  455. return nil
  456. }
  457. type accountSettingsLegacyV7 struct {
  458. AutoreplayLines *int
  459. NickEnforcement NickEnforcementMethod
  460. AllowBouncer MulticlientAllowedSetting
  461. AutoreplayJoins bool
  462. }
  463. type accountSettingsLegacyV8 struct {
  464. AutoreplayLines *int
  465. NickEnforcement NickEnforcementMethod
  466. AllowBouncer MulticlientAllowedSetting
  467. ReplayJoins ReplayJoinsSetting
  468. }
  469. // #616: change autoreplay-joins to replay-joins
  470. func schemaChangeV7ToV8(config *Config, tx *buntdb.Tx) error {
  471. prefix := "account.settings "
  472. var accounts, blobs []string
  473. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  474. var legacy accountSettingsLegacyV7
  475. var current accountSettingsLegacyV8
  476. if !strings.HasPrefix(key, prefix) {
  477. return false
  478. }
  479. account := strings.TrimPrefix(key, prefix)
  480. err := json.Unmarshal([]byte(value), &legacy)
  481. if err != nil {
  482. log.Printf("corrupt record for %s: %v\n", account, err)
  483. return true
  484. }
  485. current.AutoreplayLines = legacy.AutoreplayLines
  486. current.NickEnforcement = legacy.NickEnforcement
  487. current.AllowBouncer = legacy.AllowBouncer
  488. if legacy.AutoreplayJoins {
  489. current.ReplayJoins = ReplayJoinsAlways
  490. } else {
  491. current.ReplayJoins = ReplayJoinsCommandsOnly
  492. }
  493. blob, err := json.Marshal(current)
  494. if err != nil {
  495. log.Printf("could not marshal record for %s: %v\n", account, err)
  496. return true
  497. }
  498. accounts = append(accounts, account)
  499. blobs = append(blobs, string(blob))
  500. return true
  501. })
  502. for i, account := range accounts {
  503. tx.Set(prefix+account, blobs[i], nil)
  504. }
  505. return nil
  506. }
  507. type accountCredsLegacyV8 struct {
  508. Version uint
  509. PassphraseSalt []byte // legacy field, not used by v1 and later
  510. PassphraseHash []byte
  511. Certificate string
  512. }
  513. type accountCredsLegacyV9 struct {
  514. Version uint
  515. PassphraseSalt []byte // legacy field, not used by v1 and later
  516. PassphraseHash []byte
  517. Certfps []string
  518. }
  519. // #530: support multiple client certificate fingerprints
  520. func schemaChangeV8ToV9(config *Config, tx *buntdb.Tx) error {
  521. prefix := "account.credentials "
  522. var accounts, blobs []string
  523. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  524. var legacy accountCredsLegacyV8
  525. var current accountCredsLegacyV9
  526. if !strings.HasPrefix(key, prefix) {
  527. return false
  528. }
  529. account := strings.TrimPrefix(key, prefix)
  530. err := json.Unmarshal([]byte(value), &legacy)
  531. if err != nil {
  532. log.Printf("corrupt record for %s: %v\n", account, err)
  533. return true
  534. }
  535. current.Version = legacy.Version
  536. current.PassphraseSalt = legacy.PassphraseSalt // ugh can't get rid of this
  537. current.PassphraseHash = legacy.PassphraseHash
  538. if legacy.Certificate != "" {
  539. current.Certfps = []string{legacy.Certificate}
  540. }
  541. blob, err := json.Marshal(current)
  542. if err != nil {
  543. log.Printf("could not marshal record for %s: %v\n", account, err)
  544. return true
  545. }
  546. accounts = append(accounts, account)
  547. blobs = append(blobs, string(blob))
  548. return true
  549. })
  550. for i, account := range accounts {
  551. tx.Set(prefix+account, blobs[i], nil)
  552. }
  553. return nil
  554. }
  555. // #836: account registration time at nanosecond resolution
  556. // (mostly to simplify testing)
  557. func schemaChangeV9ToV10(config *Config, tx *buntdb.Tx) error {
  558. prefix := "account.registered.time "
  559. var accounts, times []string
  560. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  561. if !strings.HasPrefix(key, prefix) {
  562. return false
  563. }
  564. account := strings.TrimPrefix(key, prefix)
  565. accounts = append(accounts, account)
  566. times = append(times, value)
  567. return true
  568. })
  569. for i, account := range accounts {
  570. time, err := strconv.ParseInt(times[i], 10, 64)
  571. if err != nil {
  572. log.Printf("corrupt registration time entry for %s: %v\n", account, err)
  573. continue
  574. }
  575. time = time * 1000000000
  576. tx.Set(prefix+account, strconv.FormatInt(time, 10), nil)
  577. }
  578. return nil
  579. }
  580. // #952: move the cloak secret into the database,
  581. // generate a new one if necessary
  582. func schemaChangeV10ToV11(config *Config, tx *buntdb.Tx) error {
  583. cloakSecret := config.Server.Cloaks.LegacySecretValue
  584. if cloakSecret == "" || cloakSecret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
  585. cloakSecret = utils.GenerateSecretKey()
  586. }
  587. _, _, err := tx.Set(keyCloakSecret, cloakSecret, nil)
  588. return err
  589. }
  590. // #1027: NickEnforcementTimeout (2) was removed,
  591. // NickEnforcementStrict was 3 and is now 2
  592. func schemaChangeV11ToV12(config *Config, tx *buntdb.Tx) error {
  593. prefix := "account.settings "
  594. var accounts, rawSettings []string
  595. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  596. if !strings.HasPrefix(key, prefix) {
  597. return false
  598. }
  599. account := strings.TrimPrefix(key, prefix)
  600. accounts = append(accounts, account)
  601. rawSettings = append(rawSettings, value)
  602. return true
  603. })
  604. for i, account := range accounts {
  605. var settings AccountSettings
  606. err := json.Unmarshal([]byte(rawSettings[i]), &settings)
  607. if err != nil {
  608. log.Printf("corrupt account settings entry for %s: %v\n", account, err)
  609. continue
  610. }
  611. // upgrade NickEnforcementTimeout (which was 2) to NickEnforcementStrict (currently 2),
  612. // fix up the old value of NickEnforcementStrict (3) to the current value (2)
  613. if int(settings.NickEnforcement) == 3 {
  614. settings.NickEnforcement = NickEnforcementMethod(2)
  615. text, err := json.Marshal(settings)
  616. if err != nil {
  617. return err
  618. }
  619. tx.Set(prefix+account, string(text), nil)
  620. }
  621. }
  622. return nil
  623. }
  624. type accountCredsLegacyV13 struct {
  625. Version CredentialsVersion
  626. PassphraseHash []byte
  627. Certfps []string
  628. }
  629. // see #212 / #284. this packs the legacy salts into a single passphrase hash,
  630. // allowing legacy passphrases to be verified using the new API `checkLegacyPassphrase`.
  631. func schemaChangeV12ToV13(config *Config, tx *buntdb.Tx) error {
  632. salt, err := tx.Get("crypto.salt")
  633. if err != nil {
  634. return nil // no change required
  635. }
  636. tx.Delete("crypto.salt")
  637. rawSalt, err := base64.StdEncoding.DecodeString(salt)
  638. if err != nil {
  639. return nil // just throw away the creds at this point
  640. }
  641. prefix := "account.credentials "
  642. var accounts []string
  643. var credentials []accountCredsLegacyV13
  644. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  645. if !strings.HasPrefix(key, prefix) {
  646. return false
  647. }
  648. account := strings.TrimPrefix(key, prefix)
  649. var credsOld accountCredsLegacyV9
  650. err = json.Unmarshal([]byte(value), &credsOld)
  651. if err != nil {
  652. return true
  653. }
  654. // skip if these aren't legacy creds!
  655. if credsOld.Version != 0 {
  656. return true
  657. }
  658. var credsNew accountCredsLegacyV13
  659. credsNew.Version = 0 // mark hash for migration
  660. credsNew.Certfps = credsOld.Certfps
  661. credsNew.PassphraseHash = append(credsNew.PassphraseHash, rawSalt...)
  662. credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseSalt...)
  663. credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseHash...)
  664. accounts = append(accounts, account)
  665. credentials = append(credentials, credsNew)
  666. return true
  667. })
  668. for i, account := range accounts {
  669. bytesOut, err := json.Marshal(credentials[i])
  670. if err != nil {
  671. return err
  672. }
  673. _, _, err = tx.Set(prefix+account, string(bytesOut), nil)
  674. if err != nil {
  675. return err
  676. }
  677. }
  678. return nil
  679. }
  680. // channel registration time and topic set time at nanosecond resolution
  681. func schemaChangeV13ToV14(config *Config, tx *buntdb.Tx) error {
  682. prefix := "channel.registered.time "
  683. var channels, times []string
  684. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  685. if !strings.HasPrefix(key, prefix) {
  686. return false
  687. }
  688. channel := strings.TrimPrefix(key, prefix)
  689. channels = append(channels, channel)
  690. times = append(times, value)
  691. return true
  692. })
  693. billion := int64(time.Second)
  694. for i, channel := range channels {
  695. regTime, err := strconv.ParseInt(times[i], 10, 64)
  696. if err != nil {
  697. log.Printf("corrupt registration time entry for %s: %v\n", channel, err)
  698. continue
  699. }
  700. regTime = regTime * billion
  701. tx.Set(prefix+channel, strconv.FormatInt(regTime, 10), nil)
  702. topicTimeKey := "channel.topic.settime " + channel
  703. topicSetAt, err := tx.Get(topicTimeKey)
  704. if err == nil {
  705. if setTime, err := strconv.ParseInt(topicSetAt, 10, 64); err == nil {
  706. tx.Set(topicTimeKey, strconv.FormatInt(setTime*billion, 10), nil)
  707. }
  708. }
  709. }
  710. return nil
  711. }
  712. // #1327: delete any invalid klines
  713. func schemaChangeV14ToV15(config *Config, tx *buntdb.Tx) error {
  714. prefix := "bans.klinev2 "
  715. var keys []string
  716. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  717. if !strings.HasPrefix(key, prefix) {
  718. return false
  719. }
  720. if key != strings.TrimSpace(key) {
  721. keys = append(keys, key)
  722. }
  723. return true
  724. })
  725. // don't bother trying to fix these up
  726. for _, key := range keys {
  727. tx.Delete(key)
  728. }
  729. return nil
  730. }
  731. // #1330: delete any stale realname records
  732. func schemaChangeV15ToV16(config *Config, tx *buntdb.Tx) error {
  733. prefix := "account.realname "
  734. verifiedPrefix := "account.verified "
  735. var keys []string
  736. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  737. if !strings.HasPrefix(key, prefix) {
  738. return false
  739. }
  740. acct := strings.TrimPrefix(key, prefix)
  741. verifiedKey := verifiedPrefix + acct
  742. _, verifiedErr := tx.Get(verifiedKey)
  743. if verifiedErr != nil {
  744. keys = append(keys, key)
  745. }
  746. return true
  747. })
  748. for _, key := range keys {
  749. tx.Delete(key)
  750. }
  751. return nil
  752. }
  753. // #1346: remove vhost request queue
  754. func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
  755. prefix := "vhostQueue "
  756. var keys []string
  757. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  758. if !strings.HasPrefix(key, prefix) {
  759. return false
  760. }
  761. keys = append(keys, key)
  762. return true
  763. })
  764. for _, key := range keys {
  765. tx.Delete(key)
  766. }
  767. return nil
  768. }
  769. func init() {
  770. allChanges := []SchemaChange{
  771. {
  772. InitialVersion: "1",
  773. TargetVersion: "2",
  774. Changer: schemaChangeV1toV2,
  775. },
  776. {
  777. InitialVersion: "2",
  778. TargetVersion: "3",
  779. Changer: schemaChangeV2ToV3,
  780. },
  781. {
  782. InitialVersion: "3",
  783. TargetVersion: "4",
  784. Changer: schemaChangeV3ToV4,
  785. },
  786. {
  787. InitialVersion: "4",
  788. TargetVersion: "5",
  789. Changer: schemaChangeV4ToV5,
  790. },
  791. {
  792. InitialVersion: "5",
  793. TargetVersion: "6",
  794. Changer: schemaChangeV5ToV6,
  795. },
  796. {
  797. InitialVersion: "6",
  798. TargetVersion: "7",
  799. Changer: schemaChangeV6ToV7,
  800. },
  801. {
  802. InitialVersion: "7",
  803. TargetVersion: "8",
  804. Changer: schemaChangeV7ToV8,
  805. },
  806. {
  807. InitialVersion: "8",
  808. TargetVersion: "9",
  809. Changer: schemaChangeV8ToV9,
  810. },
  811. {
  812. InitialVersion: "9",
  813. TargetVersion: "10",
  814. Changer: schemaChangeV9ToV10,
  815. },
  816. {
  817. InitialVersion: "10",
  818. TargetVersion: "11",
  819. Changer: schemaChangeV10ToV11,
  820. },
  821. {
  822. InitialVersion: "11",
  823. TargetVersion: "12",
  824. Changer: schemaChangeV11ToV12,
  825. },
  826. {
  827. InitialVersion: "12",
  828. TargetVersion: "13",
  829. Changer: schemaChangeV12ToV13,
  830. },
  831. {
  832. InitialVersion: "13",
  833. TargetVersion: "14",
  834. Changer: schemaChangeV13ToV14,
  835. },
  836. {
  837. InitialVersion: "14",
  838. TargetVersion: "15",
  839. Changer: schemaChangeV14ToV15,
  840. },
  841. {
  842. InitialVersion: "15",
  843. TargetVersion: "16",
  844. Changer: schemaChangeV15ToV16,
  845. },
  846. {
  847. InitialVersion: "16",
  848. TargetVersion: "17",
  849. Changer: schemaChangeV16ToV17,
  850. },
  851. }
  852. // build the index
  853. schemaChanges = make(map[string]SchemaChange)
  854. for _, change := range allChanges {
  855. schemaChanges[change.InitialVersion] = change
  856. }
  857. }