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 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341
  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/ergochat/ergo/irc/bunt"
  15. "github.com/ergochat/ergo/irc/datastore"
  16. "github.com/ergochat/ergo/irc/modes"
  17. "github.com/ergochat/ergo/irc/utils"
  18. "github.com/tidwall/buntdb"
  19. )
  20. const (
  21. // TODO migrate metadata keys as well
  22. // 'version' of the database schema
  23. // latest schema of the db
  24. latestDbSchema = 23
  25. )
  26. var (
  27. schemaVersionUUID = utils.UUID{0, 255, 85, 13, 212, 10, 191, 121, 245, 152, 142, 89, 97, 141, 219, 87} // AP9VDdQKv3n1mI5ZYY3bVw
  28. cloakSecretUUID = utils.UUID{170, 214, 184, 208, 116, 181, 67, 75, 161, 23, 233, 16, 113, 251, 94, 229} // qta40HS1Q0uhF-kQcfte5Q
  29. keySchemaVersion = bunt.BuntKey(datastore.TableMetadata, schemaVersionUUID)
  30. keyCloakSecret = bunt.BuntKey(datastore.TableMetadata, cloakSecretUUID)
  31. )
  32. type SchemaChanger func(*Config, *buntdb.Tx) error
  33. type SchemaChange struct {
  34. InitialVersion int // the change will take this version
  35. TargetVersion int // and transform it into this version
  36. Changer SchemaChanger
  37. }
  38. func checkDBReadyForInit(path string) error {
  39. _, err := os.Stat(path)
  40. if err == nil {
  41. return fmt.Errorf("Datastore already exists (delete it manually to continue): %s", path)
  42. } else if !os.IsNotExist(err) {
  43. return fmt.Errorf("Datastore path %s is inaccessible: %w", path, err)
  44. }
  45. return nil
  46. }
  47. // InitDB creates the database, implementing the `oragono initdb` command.
  48. func InitDB(path string) error {
  49. if err := checkDBReadyForInit(path); err != nil {
  50. return err
  51. }
  52. if err := initializeDB(path); err != nil {
  53. return fmt.Errorf("Could not save datastore: %w", err)
  54. }
  55. return nil
  56. }
  57. // internal database initialization code
  58. func initializeDB(path string) error {
  59. store, err := buntdb.Open(path)
  60. if err != nil {
  61. return err
  62. }
  63. defer store.Close()
  64. err = store.Update(func(tx *buntdb.Tx) error {
  65. // set schema version
  66. tx.Set(keySchemaVersion, strconv.Itoa(latestDbSchema), nil)
  67. tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
  68. return nil
  69. })
  70. return err
  71. }
  72. // OpenDatabase returns an existing database, performing a schema version check.
  73. func OpenDatabase(config *Config) (*buntdb.DB, error) {
  74. return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
  75. }
  76. // open the database, giving it at most one chance to auto-upgrade the schema
  77. func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
  78. db, err = buntdb.Open(config.Datastore.Path)
  79. if err != nil {
  80. return
  81. }
  82. defer func() {
  83. if err != nil && db != nil {
  84. db.Close()
  85. db = nil
  86. }
  87. }()
  88. // read the current version string
  89. var version int
  90. err = db.View(func(tx *buntdb.Tx) (err error) {
  91. version, err = retrieveSchemaVersion(tx)
  92. return err
  93. })
  94. if err != nil {
  95. return
  96. }
  97. if version == latestDbSchema {
  98. // success
  99. return
  100. }
  101. // XXX quiesce the DB so we can be sure it's safe to make a backup copy
  102. db.Close()
  103. db = nil
  104. if allowAutoupgrade {
  105. err = performAutoUpgrade(version, config)
  106. if err != nil {
  107. return
  108. }
  109. // successful autoupgrade, let's try this again:
  110. return openDatabaseInternal(config, false)
  111. } else {
  112. err = &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
  113. return
  114. }
  115. }
  116. func retrieveSchemaVersion(tx *buntdb.Tx) (version int, err error) {
  117. if val, err := tx.Get(keySchemaVersion); err == nil {
  118. return strconv.Atoi(val)
  119. }
  120. // legacy key:
  121. if val, err := tx.Get("db.version"); err == nil {
  122. return strconv.Atoi(val)
  123. }
  124. return 0, buntdb.ErrNotFound
  125. }
  126. func performAutoUpgrade(currentVersion int, config *Config) (err error) {
  127. path := config.Datastore.Path
  128. log.Printf("attempting to auto-upgrade schema from version %d to %d\n", currentVersion, latestDbSchema)
  129. timestamp := time.Now().UTC().Format("2006-01-02-15.04.05.000Z")
  130. backupPath := fmt.Sprintf("%s.v%d.%s.bak", path, currentVersion, timestamp)
  131. log.Printf("making a backup of current database at %s\n", backupPath)
  132. err = utils.CopyFile(path, backupPath)
  133. if err != nil {
  134. return err
  135. }
  136. err = UpgradeDB(config)
  137. if err != nil {
  138. // database upgrade is a single transaction, so we don't need to restore the backup;
  139. // we can just delete it
  140. os.Remove(backupPath)
  141. }
  142. return err
  143. }
  144. // UpgradeDB upgrades the datastore to the latest schema.
  145. func UpgradeDB(config *Config) (err error) {
  146. // #715: test that the database exists
  147. _, err = os.Stat(config.Datastore.Path)
  148. if err != nil {
  149. return err
  150. }
  151. store, err := buntdb.Open(config.Datastore.Path)
  152. if err != nil {
  153. return err
  154. }
  155. defer store.Close()
  156. var version int
  157. err = store.Update(func(tx *buntdb.Tx) error {
  158. for {
  159. if version == 0 {
  160. version, err = retrieveSchemaVersion(tx)
  161. if err != nil {
  162. return err
  163. }
  164. }
  165. if version == latestDbSchema {
  166. // success!
  167. break
  168. }
  169. change, ok := getSchemaChange(version)
  170. if !ok {
  171. // unable to upgrade to the desired version, roll back
  172. return &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
  173. }
  174. log.Printf("attempting to update schema from version %d\n", version)
  175. err := change.Changer(config, tx)
  176. if err != nil {
  177. return err
  178. }
  179. version = change.TargetVersion
  180. _, _, err = tx.Set(keySchemaVersion, strconv.Itoa(version), nil)
  181. if err != nil {
  182. return err
  183. }
  184. log.Printf("successfully updated schema to version %d\n", version)
  185. }
  186. return nil
  187. })
  188. if err != nil {
  189. log.Printf("database upgrade failed and was rolled back: %v\n", err)
  190. }
  191. return err
  192. }
  193. func LoadCloakSecret(dstore datastore.Datastore) (result string, err error) {
  194. val, err := dstore.Get(datastore.TableMetadata, cloakSecretUUID)
  195. if err != nil {
  196. return
  197. }
  198. return string(val), nil
  199. }
  200. func StoreCloakSecret(dstore datastore.Datastore, secret string) {
  201. // TODO error checking
  202. dstore.Set(datastore.TableMetadata, cloakSecretUUID, []byte(secret), time.Time{})
  203. }
  204. func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
  205. // == version 1 -> 2 ==
  206. // account key changes and account.verified key bugfix.
  207. var keysToRemove []string
  208. newKeys := make(map[string]string)
  209. tx.AscendKeys("account *", func(key, value string) bool {
  210. keysToRemove = append(keysToRemove, key)
  211. splitkey := strings.Split(key, " ")
  212. // work around bug
  213. if splitkey[2] == "exists" {
  214. // manually create new verified key
  215. newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
  216. newKeys[newVerifiedKey] = "1"
  217. } else if splitkey[1] == "%s" {
  218. return true
  219. }
  220. newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
  221. newKeys[newKey] = value
  222. return true
  223. })
  224. for _, key := range keysToRemove {
  225. tx.Delete(key)
  226. }
  227. for key, value := range newKeys {
  228. tx.Set(key, value, nil)
  229. }
  230. return nil
  231. }
  232. // 1. channel founder names should be casefolded
  233. // 2. founder should be explicitly granted the ChannelFounder user mode
  234. // 3. explicitly initialize stored channel modes to the server default values
  235. func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
  236. var channels []string
  237. prefix := "channel.exists "
  238. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  239. if !strings.HasPrefix(key, prefix) {
  240. return false
  241. }
  242. chname := strings.TrimPrefix(key, prefix)
  243. channels = append(channels, chname)
  244. return true
  245. })
  246. // founder names should be casefolded
  247. // founder should be explicitly granted the ChannelFounder user mode
  248. for _, channel := range channels {
  249. founderKey := "channel.founder " + channel
  250. founder, _ := tx.Get(founderKey)
  251. if founder != "" {
  252. founder, err := CasefoldName(founder)
  253. if err == nil {
  254. tx.Set(founderKey, founder, nil)
  255. accountToUmode := map[string]modes.Mode{
  256. founder: modes.ChannelFounder,
  257. }
  258. atustr, _ := json.Marshal(accountToUmode)
  259. tx.Set("channel.accounttoumode "+channel, string(atustr), nil)
  260. }
  261. }
  262. }
  263. // explicitly store the channel modes
  264. defaultModes := config.Channels.defaultModes
  265. modeStrings := make([]string, len(defaultModes))
  266. for i, mode := range defaultModes {
  267. modeStrings[i] = string(mode)
  268. }
  269. defaultModeString := strings.Join(modeStrings, "")
  270. for _, channel := range channels {
  271. tx.Set("channel.modes "+channel, defaultModeString, nil)
  272. }
  273. return nil
  274. }
  275. // 1. ban info format changed (from `legacyBanInfo` below to `IPBanInfo`)
  276. // 2. dlines against individual IPs are normalized into dlines against the appropriate /128 network
  277. func schemaChangeV3ToV4(config *Config, tx *buntdb.Tx) error {
  278. type ipRestrictTime struct {
  279. Duration time.Duration
  280. Expires time.Time
  281. }
  282. type legacyBanInfo struct {
  283. Reason string `json:"reason"`
  284. OperReason string `json:"oper_reason"`
  285. OperName string `json:"oper_name"`
  286. Time *ipRestrictTime `json:"time"`
  287. }
  288. now := time.Now()
  289. legacyToNewInfo := func(old legacyBanInfo) (new_ IPBanInfo) {
  290. new_.Reason = old.Reason
  291. new_.OperReason = old.OperReason
  292. new_.OperName = old.OperName
  293. if old.Time == nil {
  294. new_.TimeCreated = now
  295. new_.Duration = 0
  296. } else {
  297. new_.TimeCreated = old.Time.Expires.Add(-1 * old.Time.Duration)
  298. new_.Duration = old.Time.Duration
  299. }
  300. return
  301. }
  302. var keysToDelete []string
  303. prefix := "bans.dline "
  304. dlines := make(map[string]IPBanInfo)
  305. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  306. if !strings.HasPrefix(key, prefix) {
  307. return false
  308. }
  309. keysToDelete = append(keysToDelete, key)
  310. var lbinfo legacyBanInfo
  311. id := strings.TrimPrefix(key, prefix)
  312. err := json.Unmarshal([]byte(value), &lbinfo)
  313. if err != nil {
  314. log.Printf("error unmarshaling legacy dline: %v\n", err)
  315. return true
  316. }
  317. // legacy keys can be either an IP or a CIDR
  318. hostNet, err := utils.NormalizedNetFromString(id)
  319. if err != nil {
  320. log.Printf("error unmarshaling legacy dline network: %v\n", err)
  321. return true
  322. }
  323. dlines[utils.NetToNormalizedString(hostNet)] = legacyToNewInfo(lbinfo)
  324. return true
  325. })
  326. setOptions := func(info IPBanInfo) *buntdb.SetOptions {
  327. if info.Duration == 0 {
  328. return nil
  329. }
  330. ttl := info.TimeCreated.Add(info.Duration).Sub(now)
  331. return &buntdb.SetOptions{Expires: true, TTL: ttl}
  332. }
  333. // store the new dlines
  334. for id, info := range dlines {
  335. b, err := json.Marshal(info)
  336. if err != nil {
  337. log.Printf("error marshaling migrated dline: %v\n", err)
  338. continue
  339. }
  340. tx.Set(fmt.Sprintf("bans.dlinev2 %s", id), string(b), setOptions(info))
  341. }
  342. // same operations against klines
  343. prefix = "bans.kline "
  344. klines := make(map[string]IPBanInfo)
  345. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  346. if !strings.HasPrefix(key, prefix) {
  347. return false
  348. }
  349. keysToDelete = append(keysToDelete, key)
  350. mask := strings.TrimPrefix(key, prefix)
  351. var lbinfo legacyBanInfo
  352. err := json.Unmarshal([]byte(value), &lbinfo)
  353. if err != nil {
  354. log.Printf("error unmarshaling legacy kline: %v\n", err)
  355. return true
  356. }
  357. klines[mask] = legacyToNewInfo(lbinfo)
  358. return true
  359. })
  360. for mask, info := range klines {
  361. b, err := json.Marshal(info)
  362. if err != nil {
  363. log.Printf("error marshaling migrated kline: %v\n", err)
  364. continue
  365. }
  366. tx.Set(fmt.Sprintf("bans.klinev2 %s", mask), string(b), setOptions(info))
  367. }
  368. // clean up all the old entries
  369. for _, key := range keysToDelete {
  370. tx.Delete(key)
  371. }
  372. return nil
  373. }
  374. // create new key tracking channels that belong to an account
  375. func schemaChangeV4ToV5(config *Config, tx *buntdb.Tx) error {
  376. founderToChannels := make(map[string][]string)
  377. prefix := "channel.founder "
  378. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  379. if !strings.HasPrefix(key, prefix) {
  380. return false
  381. }
  382. channel := strings.TrimPrefix(key, prefix)
  383. founderToChannels[value] = append(founderToChannels[value], channel)
  384. return true
  385. })
  386. for founder, channels := range founderToChannels {
  387. tx.Set(fmt.Sprintf("account.channels %s", founder), strings.Join(channels, ","), nil)
  388. }
  389. return nil
  390. }
  391. // custom nick enforcement was a separate db key, now it's part of settings
  392. func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
  393. accountToEnforcement := make(map[string]NickEnforcementMethod)
  394. prefix := "account.customenforcement "
  395. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  396. if !strings.HasPrefix(key, prefix) {
  397. return false
  398. }
  399. account := strings.TrimPrefix(key, prefix)
  400. method, err := nickReservationFromString(value)
  401. if err == nil {
  402. accountToEnforcement[account] = method
  403. } else {
  404. log.Printf("skipping corrupt custom enforcement value for %s\n", account)
  405. }
  406. return true
  407. })
  408. for account, method := range accountToEnforcement {
  409. var settings AccountSettings
  410. settings.NickEnforcement = method
  411. text, err := json.Marshal(settings)
  412. if err != nil {
  413. return err
  414. }
  415. tx.Delete(prefix + account)
  416. tx.Set(fmt.Sprintf("account.settings %s", account), string(text), nil)
  417. }
  418. return nil
  419. }
  420. type maskInfoV7 struct {
  421. TimeCreated time.Time
  422. CreatorNickmask string
  423. CreatorAccount string
  424. }
  425. func schemaChangeV6ToV7(config *Config, tx *buntdb.Tx) error {
  426. now := time.Now().UTC()
  427. var channels []string
  428. prefix := "channel.exists "
  429. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  430. if !strings.HasPrefix(key, prefix) {
  431. return false
  432. }
  433. channels = append(channels, strings.TrimPrefix(key, prefix))
  434. return true
  435. })
  436. converter := func(key string) {
  437. oldRawValue, err := tx.Get(key)
  438. if err != nil {
  439. return
  440. }
  441. var masks []string
  442. err = json.Unmarshal([]byte(oldRawValue), &masks)
  443. if err != nil {
  444. return
  445. }
  446. newCookedValue := make(map[string]maskInfoV7)
  447. for _, mask := range masks {
  448. normalizedMask, err := CanonicalizeMaskWildcard(mask)
  449. if err != nil {
  450. continue
  451. }
  452. newCookedValue[normalizedMask] = maskInfoV7{
  453. TimeCreated: now,
  454. CreatorNickmask: "*",
  455. CreatorAccount: "*",
  456. }
  457. }
  458. newRawValue, err := json.Marshal(newCookedValue)
  459. if err != nil {
  460. return
  461. }
  462. tx.Set(key, string(newRawValue), nil)
  463. }
  464. prefixes := []string{
  465. "channel.banlist %s",
  466. "channel.exceptlist %s",
  467. "channel.invitelist %s",
  468. }
  469. for _, channel := range channels {
  470. for _, prefix := range prefixes {
  471. converter(fmt.Sprintf(prefix, channel))
  472. }
  473. }
  474. return nil
  475. }
  476. type accountSettingsLegacyV7 struct {
  477. AutoreplayLines *int
  478. NickEnforcement NickEnforcementMethod
  479. AllowBouncer MulticlientAllowedSetting
  480. AutoreplayJoins bool
  481. }
  482. type accountSettingsLegacyV8 struct {
  483. AutoreplayLines *int
  484. NickEnforcement NickEnforcementMethod
  485. AllowBouncer MulticlientAllowedSetting
  486. ReplayJoins ReplayJoinsSetting
  487. }
  488. // #616: change autoreplay-joins to replay-joins
  489. func schemaChangeV7ToV8(config *Config, tx *buntdb.Tx) error {
  490. prefix := "account.settings "
  491. var accounts, blobs []string
  492. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  493. var legacy accountSettingsLegacyV7
  494. var current accountSettingsLegacyV8
  495. if !strings.HasPrefix(key, prefix) {
  496. return false
  497. }
  498. account := strings.TrimPrefix(key, prefix)
  499. err := json.Unmarshal([]byte(value), &legacy)
  500. if err != nil {
  501. log.Printf("corrupt record for %s: %v\n", account, err)
  502. return true
  503. }
  504. current.AutoreplayLines = legacy.AutoreplayLines
  505. current.NickEnforcement = legacy.NickEnforcement
  506. current.AllowBouncer = legacy.AllowBouncer
  507. if legacy.AutoreplayJoins {
  508. current.ReplayJoins = ReplayJoinsAlways
  509. } else {
  510. current.ReplayJoins = ReplayJoinsCommandsOnly
  511. }
  512. blob, err := json.Marshal(current)
  513. if err != nil {
  514. log.Printf("could not marshal record for %s: %v\n", account, err)
  515. return true
  516. }
  517. accounts = append(accounts, account)
  518. blobs = append(blobs, string(blob))
  519. return true
  520. })
  521. for i, account := range accounts {
  522. tx.Set(prefix+account, blobs[i], nil)
  523. }
  524. return nil
  525. }
  526. type accountCredsLegacyV8 struct {
  527. Version uint
  528. PassphraseSalt []byte // legacy field, not used by v1 and later
  529. PassphraseHash []byte
  530. Certificate string
  531. }
  532. type accountCredsLegacyV9 struct {
  533. Version uint
  534. PassphraseSalt []byte // legacy field, not used by v1 and later
  535. PassphraseHash []byte
  536. Certfps []string
  537. }
  538. // #530: support multiple client certificate fingerprints
  539. func schemaChangeV8ToV9(config *Config, tx *buntdb.Tx) error {
  540. prefix := "account.credentials "
  541. var accounts, blobs []string
  542. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  543. var legacy accountCredsLegacyV8
  544. var current accountCredsLegacyV9
  545. if !strings.HasPrefix(key, prefix) {
  546. return false
  547. }
  548. account := strings.TrimPrefix(key, prefix)
  549. err := json.Unmarshal([]byte(value), &legacy)
  550. if err != nil {
  551. log.Printf("corrupt record for %s: %v\n", account, err)
  552. return true
  553. }
  554. current.Version = legacy.Version
  555. current.PassphraseSalt = legacy.PassphraseSalt // ugh can't get rid of this
  556. current.PassphraseHash = legacy.PassphraseHash
  557. if legacy.Certificate != "" {
  558. current.Certfps = []string{legacy.Certificate}
  559. }
  560. blob, err := json.Marshal(current)
  561. if err != nil {
  562. log.Printf("could not marshal record for %s: %v\n", account, err)
  563. return true
  564. }
  565. accounts = append(accounts, account)
  566. blobs = append(blobs, string(blob))
  567. return true
  568. })
  569. for i, account := range accounts {
  570. tx.Set(prefix+account, blobs[i], nil)
  571. }
  572. return nil
  573. }
  574. // #836: account registration time at nanosecond resolution
  575. // (mostly to simplify testing)
  576. func schemaChangeV9ToV10(config *Config, tx *buntdb.Tx) error {
  577. prefix := "account.registered.time "
  578. var accounts, times []string
  579. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  580. if !strings.HasPrefix(key, prefix) {
  581. return false
  582. }
  583. account := strings.TrimPrefix(key, prefix)
  584. accounts = append(accounts, account)
  585. times = append(times, value)
  586. return true
  587. })
  588. for i, account := range accounts {
  589. time, err := strconv.ParseInt(times[i], 10, 64)
  590. if err != nil {
  591. log.Printf("corrupt registration time entry for %s: %v\n", account, err)
  592. continue
  593. }
  594. time = time * 1000000000
  595. tx.Set(prefix+account, strconv.FormatInt(time, 10), nil)
  596. }
  597. return nil
  598. }
  599. // #952: move the cloak secret into the database,
  600. // generate a new one if necessary
  601. func schemaChangeV10ToV11(config *Config, tx *buntdb.Tx) error {
  602. cloakSecret := config.Server.Cloaks.LegacySecretValue
  603. if cloakSecret == "" || cloakSecret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
  604. cloakSecret = utils.GenerateSecretKey()
  605. }
  606. _, _, err := tx.Set(keyCloakSecret, cloakSecret, nil)
  607. return err
  608. }
  609. // #1027: NickEnforcementTimeout (2) was removed,
  610. // NickEnforcementStrict was 3 and is now 2
  611. func schemaChangeV11ToV12(config *Config, tx *buntdb.Tx) error {
  612. prefix := "account.settings "
  613. var accounts, rawSettings []string
  614. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  615. if !strings.HasPrefix(key, prefix) {
  616. return false
  617. }
  618. account := strings.TrimPrefix(key, prefix)
  619. accounts = append(accounts, account)
  620. rawSettings = append(rawSettings, value)
  621. return true
  622. })
  623. for i, account := range accounts {
  624. var settings AccountSettings
  625. err := json.Unmarshal([]byte(rawSettings[i]), &settings)
  626. if err != nil {
  627. log.Printf("corrupt account settings entry for %s: %v\n", account, err)
  628. continue
  629. }
  630. // upgrade NickEnforcementTimeout (which was 2) to NickEnforcementStrict (currently 2),
  631. // fix up the old value of NickEnforcementStrict (3) to the current value (2)
  632. if int(settings.NickEnforcement) == 3 {
  633. settings.NickEnforcement = NickEnforcementMethod(2)
  634. text, err := json.Marshal(settings)
  635. if err != nil {
  636. return err
  637. }
  638. tx.Set(prefix+account, string(text), nil)
  639. }
  640. }
  641. return nil
  642. }
  643. type accountCredsLegacyV13 struct {
  644. Version CredentialsVersion
  645. PassphraseHash []byte
  646. Certfps []string
  647. }
  648. // see #212 / #284. this packs the legacy salts into a single passphrase hash,
  649. // allowing legacy passphrases to be verified using the new API `checkLegacyPassphrase`.
  650. func schemaChangeV12ToV13(config *Config, tx *buntdb.Tx) error {
  651. salt, err := tx.Get("crypto.salt")
  652. if err != nil {
  653. return nil // no change required
  654. }
  655. tx.Delete("crypto.salt")
  656. rawSalt, err := base64.StdEncoding.DecodeString(salt)
  657. if err != nil {
  658. return nil // just throw away the creds at this point
  659. }
  660. prefix := "account.credentials "
  661. var accounts []string
  662. var credentials []accountCredsLegacyV13
  663. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  664. if !strings.HasPrefix(key, prefix) {
  665. return false
  666. }
  667. account := strings.TrimPrefix(key, prefix)
  668. var credsOld accountCredsLegacyV9
  669. err = json.Unmarshal([]byte(value), &credsOld)
  670. if err != nil {
  671. return true
  672. }
  673. // skip if these aren't legacy creds!
  674. if credsOld.Version != 0 {
  675. return true
  676. }
  677. var credsNew accountCredsLegacyV13
  678. credsNew.Version = 0 // mark hash for migration
  679. credsNew.Certfps = credsOld.Certfps
  680. credsNew.PassphraseHash = append(credsNew.PassphraseHash, rawSalt...)
  681. credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseSalt...)
  682. credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseHash...)
  683. accounts = append(accounts, account)
  684. credentials = append(credentials, credsNew)
  685. return true
  686. })
  687. for i, account := range accounts {
  688. bytesOut, err := json.Marshal(credentials[i])
  689. if err != nil {
  690. return err
  691. }
  692. _, _, err = tx.Set(prefix+account, string(bytesOut), nil)
  693. if err != nil {
  694. return err
  695. }
  696. }
  697. return nil
  698. }
  699. // channel registration time and topic set time at nanosecond resolution
  700. func schemaChangeV13ToV14(config *Config, tx *buntdb.Tx) error {
  701. prefix := "channel.registered.time "
  702. var channels, times []string
  703. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  704. if !strings.HasPrefix(key, prefix) {
  705. return false
  706. }
  707. channel := strings.TrimPrefix(key, prefix)
  708. channels = append(channels, channel)
  709. times = append(times, value)
  710. return true
  711. })
  712. billion := int64(time.Second)
  713. for i, channel := range channels {
  714. regTime, err := strconv.ParseInt(times[i], 10, 64)
  715. if err != nil {
  716. log.Printf("corrupt registration time entry for %s: %v\n", channel, err)
  717. continue
  718. }
  719. regTime = regTime * billion
  720. tx.Set(prefix+channel, strconv.FormatInt(regTime, 10), nil)
  721. topicTimeKey := "channel.topic.settime " + channel
  722. topicSetAt, err := tx.Get(topicTimeKey)
  723. if err == nil {
  724. if setTime, err := strconv.ParseInt(topicSetAt, 10, 64); err == nil {
  725. tx.Set(topicTimeKey, strconv.FormatInt(setTime*billion, 10), nil)
  726. }
  727. }
  728. }
  729. return nil
  730. }
  731. // #1327: delete any invalid klines
  732. func schemaChangeV14ToV15(config *Config, tx *buntdb.Tx) error {
  733. prefix := "bans.klinev2 "
  734. var keys []string
  735. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  736. if !strings.HasPrefix(key, prefix) {
  737. return false
  738. }
  739. if key != strings.TrimSpace(key) {
  740. keys = append(keys, key)
  741. }
  742. return true
  743. })
  744. // don't bother trying to fix these up
  745. for _, key := range keys {
  746. tx.Delete(key)
  747. }
  748. return nil
  749. }
  750. // #1330: delete any stale realname records
  751. func schemaChangeV15ToV16(config *Config, tx *buntdb.Tx) error {
  752. prefix := "account.realname "
  753. verifiedPrefix := "account.verified "
  754. var keys []string
  755. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  756. if !strings.HasPrefix(key, prefix) {
  757. return false
  758. }
  759. acct := strings.TrimPrefix(key, prefix)
  760. verifiedKey := verifiedPrefix + acct
  761. _, verifiedErr := tx.Get(verifiedKey)
  762. if verifiedErr != nil {
  763. keys = append(keys, key)
  764. }
  765. return true
  766. })
  767. for _, key := range keys {
  768. tx.Delete(key)
  769. }
  770. return nil
  771. }
  772. // #1346: remove vhost request queue
  773. func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
  774. prefix := "vhostQueue "
  775. var keys []string
  776. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  777. if !strings.HasPrefix(key, prefix) {
  778. return false
  779. }
  780. keys = append(keys, key)
  781. return true
  782. })
  783. for _, key := range keys {
  784. tx.Delete(key)
  785. }
  786. return nil
  787. }
  788. // #1274: we used to suspend accounts by deleting their "verified" key,
  789. // now we save some metadata under a new key
  790. func schemaChangeV17ToV18(config *Config, tx *buntdb.Tx) error {
  791. now := time.Now().UTC()
  792. exists := "account.exists "
  793. suspended := "account.suspended "
  794. verif := "account.verified "
  795. verifCode := "account.verificationcode "
  796. var accounts []string
  797. tx.AscendGreaterOrEqual("", exists, func(key, value string) bool {
  798. if !strings.HasPrefix(key, exists) {
  799. return false
  800. }
  801. account := strings.TrimPrefix(key, exists)
  802. _, verifiedErr := tx.Get(verif + account)
  803. _, verifCodeErr := tx.Get(verifCode + account)
  804. if verifiedErr != nil && verifCodeErr != nil {
  805. // verified key not present, but there's no code either,
  806. // this is a suspension
  807. accounts = append(accounts, account)
  808. }
  809. return true
  810. })
  811. type accountSuspensionV18 struct {
  812. TimeCreated time.Time
  813. Duration time.Duration
  814. OperName string
  815. Reason string
  816. }
  817. for _, account := range accounts {
  818. var sus accountSuspensionV18
  819. sus.TimeCreated = now
  820. sus.OperName = "*"
  821. sus.Reason = "[unknown]"
  822. susBytes, err := json.Marshal(sus)
  823. if err != nil {
  824. return err
  825. }
  826. tx.Set(suspended+account, string(susBytes), nil)
  827. }
  828. return nil
  829. }
  830. // #1345: persist the channel-user modes of always-on clients
  831. func schemaChangeV18To19(config *Config, tx *buntdb.Tx) error {
  832. channelToAmodesCache := make(map[string]map[string]modes.Mode)
  833. joinedto := "account.joinedto "
  834. var accounts []string
  835. var channels [][]string
  836. tx.AscendGreaterOrEqual("", joinedto, func(key, value string) bool {
  837. if !strings.HasPrefix(key, joinedto) {
  838. return false
  839. }
  840. accounts = append(accounts, strings.TrimPrefix(key, joinedto))
  841. var ch []string
  842. if value != "" {
  843. ch = strings.Split(value, ",")
  844. }
  845. channels = append(channels, ch)
  846. return true
  847. })
  848. for i := 0; i < len(accounts); i++ {
  849. account := accounts[i]
  850. channels := channels[i]
  851. tx.Delete(joinedto + account)
  852. newValue := make(map[string]string, len(channels))
  853. for _, channel := range channels {
  854. chcfname, err := CasefoldChannel(channel)
  855. if err != nil {
  856. continue
  857. }
  858. // get amodes from the channelToAmodesCache, fill if necessary
  859. amodes, ok := channelToAmodesCache[chcfname]
  860. if !ok {
  861. amodeStr, _ := tx.Get("channel.accounttoumode " + chcfname)
  862. if amodeStr != "" {
  863. jErr := json.Unmarshal([]byte(amodeStr), &amodes)
  864. if jErr != nil {
  865. log.Printf("error retrieving amodes for %s: %v\n", channel, jErr)
  866. amodes = nil
  867. }
  868. }
  869. // setting/using the nil value here is ok
  870. channelToAmodesCache[chcfname] = amodes
  871. }
  872. if mode, ok := amodes[account]; ok {
  873. newValue[channel] = string(mode)
  874. } else {
  875. newValue[channel] = ""
  876. }
  877. }
  878. newValueBytes, jErr := json.Marshal(newValue)
  879. if jErr != nil {
  880. log.Printf("couldn't serialize new mode values for v19: %v\n", jErr)
  881. continue
  882. }
  883. tx.Set("account.channeltomodes "+account, string(newValueBytes), nil)
  884. }
  885. return nil
  886. }
  887. // #1490: start tracking join times for always-on clients
  888. func schemaChangeV19To20(config *Config, tx *buntdb.Tx) error {
  889. type joinData struct {
  890. Modes string
  891. JoinTime int64
  892. }
  893. var accounts []string
  894. var data []string
  895. now := time.Now().UnixNano()
  896. prefix := "account.channeltomodes "
  897. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  898. if !strings.HasPrefix(key, prefix) {
  899. return false
  900. }
  901. accounts = append(accounts, strings.TrimPrefix(key, prefix))
  902. data = append(data, value)
  903. return true
  904. })
  905. for i, account := range accounts {
  906. var existingMap map[string]string
  907. err := json.Unmarshal([]byte(data[i]), &existingMap)
  908. if err != nil {
  909. return err
  910. }
  911. newMap := make(map[string]joinData)
  912. for channel, modeStr := range existingMap {
  913. newMap[channel] = joinData{
  914. Modes: modeStr,
  915. JoinTime: now,
  916. }
  917. }
  918. serialized, err := json.Marshal(newMap)
  919. if err != nil {
  920. return err
  921. }
  922. tx.Set(prefix+account, string(serialized), nil)
  923. }
  924. return nil
  925. }
  926. // #734: move the email address into the settings object,
  927. // giving people a way to change it
  928. func schemaChangeV20To21(config *Config, tx *buntdb.Tx) error {
  929. type accountSettingsv21 struct {
  930. AutoreplayLines *int
  931. NickEnforcement NickEnforcementMethod
  932. AllowBouncer MulticlientAllowedSetting
  933. ReplayJoins ReplayJoinsSetting
  934. AlwaysOn PersistentStatus
  935. AutoreplayMissed bool
  936. DMHistory HistoryStatus
  937. AutoAway PersistentStatus
  938. Email string
  939. }
  940. var accounts []string
  941. var emails []string
  942. callbackPrefix := "account.callback "
  943. tx.AscendGreaterOrEqual("", callbackPrefix, func(key, value string) bool {
  944. if !strings.HasPrefix(key, callbackPrefix) {
  945. return false
  946. }
  947. account := strings.TrimPrefix(key, callbackPrefix)
  948. if _, err := tx.Get("account.verified " + account); err != nil {
  949. return true
  950. }
  951. if strings.HasPrefix(value, "mailto:") {
  952. accounts = append(accounts, account)
  953. emails = append(emails, strings.TrimPrefix(value, "mailto:"))
  954. }
  955. return true
  956. })
  957. for i, account := range accounts {
  958. var settings accountSettingsv21
  959. email := emails[i]
  960. settingsKey := "account.settings " + account
  961. settingsStr, err := tx.Get(settingsKey)
  962. if err == nil && settingsStr != "" {
  963. json.Unmarshal([]byte(settingsStr), &settings)
  964. }
  965. settings.Email = email
  966. settingsBytes, err := json.Marshal(settings)
  967. if err != nil {
  968. log.Printf("couldn't marshal settings for %s: %v\n", account, err)
  969. } else {
  970. tx.Set(settingsKey, string(settingsBytes), nil)
  971. }
  972. tx.Delete(callbackPrefix + account)
  973. }
  974. return nil
  975. }
  976. // #1676: we used to have ReplayJoinsNever, now it's desupported
  977. func schemaChangeV21To22(config *Config, tx *buntdb.Tx) error {
  978. type accountSettingsv22 struct {
  979. AutoreplayLines *int
  980. NickEnforcement NickEnforcementMethod
  981. AllowBouncer MulticlientAllowedSetting
  982. ReplayJoins ReplayJoinsSetting
  983. AlwaysOn PersistentStatus
  984. AutoreplayMissed bool
  985. DMHistory HistoryStatus
  986. AutoAway PersistentStatus
  987. Email string
  988. }
  989. var accounts []string
  990. var serializedSettings []string
  991. settingsPrefix := "account.settings "
  992. tx.AscendGreaterOrEqual("", settingsPrefix, func(key, value string) bool {
  993. if !strings.HasPrefix(key, settingsPrefix) {
  994. return false
  995. }
  996. if value == "" {
  997. return true
  998. }
  999. account := strings.TrimPrefix(key, settingsPrefix)
  1000. if _, err := tx.Get("account.verified " + account); err != nil {
  1001. return true
  1002. }
  1003. var settings accountSettingsv22
  1004. err := json.Unmarshal([]byte(value), &settings)
  1005. if err != nil {
  1006. log.Printf("error (v21-22) processing settings for %s: %v\n", account, err)
  1007. return true
  1008. }
  1009. // if necessary, change ReplayJoinsNever (2) to ReplayJoinsCommandsOnly (0)
  1010. if settings.ReplayJoins == ReplayJoinsSetting(2) {
  1011. settings.ReplayJoins = ReplayJoinsSetting(0)
  1012. if b, err := json.Marshal(settings); err == nil {
  1013. accounts = append(accounts, account)
  1014. serializedSettings = append(serializedSettings, string(b))
  1015. } else {
  1016. log.Printf("error (v21-22) processing settings for %s: %v\n", account, err)
  1017. }
  1018. }
  1019. return true
  1020. })
  1021. for i, account := range accounts {
  1022. tx.Set(settingsPrefix+account, serializedSettings[i], nil)
  1023. }
  1024. return nil
  1025. }
  1026. // first phase of document-oriented database refactor: channels
  1027. func schemaChangeV22ToV23(config *Config, tx *buntdb.Tx) error {
  1028. keyChannelExists := "channel.exists "
  1029. var channelNames []string
  1030. tx.AscendGreaterOrEqual("", keyChannelExists, func(key, value string) bool {
  1031. if !strings.HasPrefix(key, keyChannelExists) {
  1032. return false
  1033. }
  1034. channelNames = append(channelNames, strings.TrimPrefix(key, keyChannelExists))
  1035. return true
  1036. })
  1037. for _, channelName := range channelNames {
  1038. channel, err := loadLegacyChannel(tx, channelName)
  1039. if err != nil {
  1040. log.Printf("error loading legacy channel %s: %v", channelName, err)
  1041. continue
  1042. }
  1043. channel.UUID = utils.GenerateUUIDv4()
  1044. newKey := bunt.BuntKey(datastore.TableChannels, channel.UUID)
  1045. j, err := json.Marshal(channel)
  1046. if err != nil {
  1047. log.Printf("error marshaling channel %s: %v", channelName, err)
  1048. continue
  1049. }
  1050. tx.Set(newKey, string(j), nil)
  1051. deleteLegacyChannel(tx, channelName)
  1052. }
  1053. // purges
  1054. keyChannelPurged := "channel.purged "
  1055. var purgeKeys []string
  1056. var channelPurges []ChannelPurgeRecord
  1057. tx.AscendGreaterOrEqual("", keyChannelPurged, func(key, value string) bool {
  1058. if !strings.HasPrefix(key, keyChannelPurged) {
  1059. return false
  1060. }
  1061. purgeKeys = append(purgeKeys, key)
  1062. cfname := strings.TrimPrefix(key, keyChannelPurged)
  1063. var record ChannelPurgeRecord
  1064. err := json.Unmarshal([]byte(value), &record)
  1065. if err != nil {
  1066. log.Printf("error unmarshaling channel purge for %s: %v", cfname, err)
  1067. return true
  1068. }
  1069. record.NameCasefolded = cfname
  1070. record.UUID = utils.GenerateUUIDv4()
  1071. channelPurges = append(channelPurges, record)
  1072. return true
  1073. })
  1074. for _, record := range channelPurges {
  1075. newKey := bunt.BuntKey(datastore.TableChannelPurges, record.UUID)
  1076. j, err := json.Marshal(record)
  1077. if err != nil {
  1078. log.Printf("error marshaling channel purge %s: %v", record.NameCasefolded, err)
  1079. continue
  1080. }
  1081. tx.Set(newKey, string(j), nil)
  1082. }
  1083. for _, purgeKey := range purgeKeys {
  1084. tx.Delete(purgeKey)
  1085. }
  1086. // clean up denormalized account-to-channels mapping
  1087. keyAccountChannels := "account.channels "
  1088. var accountToChannels []string
  1089. tx.AscendGreaterOrEqual("", keyAccountChannels, func(key, value string) bool {
  1090. if !strings.HasPrefix(key, keyAccountChannels) {
  1091. return false
  1092. }
  1093. accountToChannels = append(accountToChannels, key)
  1094. return true
  1095. })
  1096. for _, key := range accountToChannels {
  1097. tx.Delete(key)
  1098. }
  1099. // migrate cloak secret
  1100. val, _ := tx.Get("crypto.cloak_secret")
  1101. tx.Set(keyCloakSecret, val, nil)
  1102. // bump the legacy version key to mark the database as downgrade-incompatible
  1103. tx.Set("db.version", "23", nil)
  1104. return nil
  1105. }
  1106. func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
  1107. for _, change := range allChanges {
  1108. if initialVersion == change.InitialVersion {
  1109. return change, true
  1110. }
  1111. }
  1112. return
  1113. }
  1114. var allChanges = []SchemaChange{
  1115. {
  1116. InitialVersion: 1,
  1117. TargetVersion: 2,
  1118. Changer: schemaChangeV1toV2,
  1119. },
  1120. {
  1121. InitialVersion: 2,
  1122. TargetVersion: 3,
  1123. Changer: schemaChangeV2ToV3,
  1124. },
  1125. {
  1126. InitialVersion: 3,
  1127. TargetVersion: 4,
  1128. Changer: schemaChangeV3ToV4,
  1129. },
  1130. {
  1131. InitialVersion: 4,
  1132. TargetVersion: 5,
  1133. Changer: schemaChangeV4ToV5,
  1134. },
  1135. {
  1136. InitialVersion: 5,
  1137. TargetVersion: 6,
  1138. Changer: schemaChangeV5ToV6,
  1139. },
  1140. {
  1141. InitialVersion: 6,
  1142. TargetVersion: 7,
  1143. Changer: schemaChangeV6ToV7,
  1144. },
  1145. {
  1146. InitialVersion: 7,
  1147. TargetVersion: 8,
  1148. Changer: schemaChangeV7ToV8,
  1149. },
  1150. {
  1151. InitialVersion: 8,
  1152. TargetVersion: 9,
  1153. Changer: schemaChangeV8ToV9,
  1154. },
  1155. {
  1156. InitialVersion: 9,
  1157. TargetVersion: 10,
  1158. Changer: schemaChangeV9ToV10,
  1159. },
  1160. {
  1161. InitialVersion: 10,
  1162. TargetVersion: 11,
  1163. Changer: schemaChangeV10ToV11,
  1164. },
  1165. {
  1166. InitialVersion: 11,
  1167. TargetVersion: 12,
  1168. Changer: schemaChangeV11ToV12,
  1169. },
  1170. {
  1171. InitialVersion: 12,
  1172. TargetVersion: 13,
  1173. Changer: schemaChangeV12ToV13,
  1174. },
  1175. {
  1176. InitialVersion: 13,
  1177. TargetVersion: 14,
  1178. Changer: schemaChangeV13ToV14,
  1179. },
  1180. {
  1181. InitialVersion: 14,
  1182. TargetVersion: 15,
  1183. Changer: schemaChangeV14ToV15,
  1184. },
  1185. {
  1186. InitialVersion: 15,
  1187. TargetVersion: 16,
  1188. Changer: schemaChangeV15ToV16,
  1189. },
  1190. {
  1191. InitialVersion: 16,
  1192. TargetVersion: 17,
  1193. Changer: schemaChangeV16ToV17,
  1194. },
  1195. {
  1196. InitialVersion: 17,
  1197. TargetVersion: 18,
  1198. Changer: schemaChangeV17ToV18,
  1199. },
  1200. {
  1201. InitialVersion: 18,
  1202. TargetVersion: 19,
  1203. Changer: schemaChangeV18To19,
  1204. },
  1205. {
  1206. InitialVersion: 19,
  1207. TargetVersion: 20,
  1208. Changer: schemaChangeV19To20,
  1209. },
  1210. {
  1211. InitialVersion: 20,
  1212. TargetVersion: 21,
  1213. Changer: schemaChangeV20To21,
  1214. },
  1215. {
  1216. InitialVersion: 21,
  1217. TargetVersion: 22,
  1218. Changer: schemaChangeV21To22,
  1219. },
  1220. {
  1221. InitialVersion: 22,
  1222. TargetVersion: 23,
  1223. Changer: schemaChangeV22ToV23,
  1224. },
  1225. }