您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

database.go 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. "strings"
  12. "github.com/oragono/oragono/irc/modes"
  13. "github.com/oragono/oragono/irc/passwd"
  14. "github.com/tidwall/buntdb"
  15. )
  16. const (
  17. // 'version' of the database schema
  18. keySchemaVersion = "db.version"
  19. // latest schema of the db
  20. latestDbSchema = "3"
  21. // key for the primary salt used by the ircd
  22. keySalt = "crypto.salt"
  23. )
  24. type SchemaChanger func(*Config, *buntdb.Tx) error
  25. type SchemaChange struct {
  26. InitialVersion string // the change will take this version
  27. TargetVersion string // and transform it into this version
  28. Changer SchemaChanger
  29. }
  30. // maps an initial version to a schema change capable of upgrading it
  31. var schemaChanges map[string]SchemaChange
  32. // InitDB creates the database.
  33. func InitDB(path string) {
  34. // prepare kvstore db
  35. //TODO(dan): fail if already exists instead? don't want to overwrite good data
  36. os.Remove(path)
  37. store, err := buntdb.Open(path)
  38. if err != nil {
  39. log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
  40. }
  41. defer store.Close()
  42. err = store.Update(func(tx *buntdb.Tx) error {
  43. // set base db salt
  44. salt, err := passwd.NewSalt()
  45. encodedSalt := base64.StdEncoding.EncodeToString(salt)
  46. if err != nil {
  47. log.Fatal("Could not generate cryptographically-secure salt for the user:", err.Error())
  48. }
  49. tx.Set(keySalt, encodedSalt, nil)
  50. // set schema version
  51. tx.Set(keySchemaVersion, latestDbSchema, nil)
  52. return nil
  53. })
  54. if err != nil {
  55. log.Fatal("Could not save datastore:", err.Error())
  56. }
  57. }
  58. // OpenDatabase returns an existing database, performing a schema version check.
  59. func OpenDatabase(path string) (*buntdb.DB, error) {
  60. // open data store
  61. db, err := buntdb.Open(path)
  62. if err != nil {
  63. return nil, err
  64. }
  65. // check db version
  66. err = db.View(func(tx *buntdb.Tx) error {
  67. version, _ := tx.Get(keySchemaVersion)
  68. if version != latestDbSchema {
  69. return fmt.Errorf("Database must be updated. Expected schema v%s, got v%s", latestDbSchema, version)
  70. }
  71. return nil
  72. })
  73. if err != nil {
  74. // close the db
  75. db.Close()
  76. return nil, err
  77. }
  78. return db, nil
  79. }
  80. // UpgradeDB upgrades the datastore to the latest schema.
  81. func UpgradeDB(config *Config) {
  82. store, err := buntdb.Open(config.Datastore.Path)
  83. if err != nil {
  84. log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
  85. }
  86. defer store.Close()
  87. var version string
  88. err = store.Update(func(tx *buntdb.Tx) error {
  89. for {
  90. version, _ = tx.Get(keySchemaVersion)
  91. change, schemaNeedsChange := schemaChanges[version]
  92. if !schemaNeedsChange {
  93. break
  94. }
  95. log.Println("attempting to update store from version " + version)
  96. err := change.Changer(config, tx)
  97. if err != nil {
  98. return err
  99. }
  100. _, _, err = tx.Set(keySchemaVersion, change.TargetVersion, nil)
  101. if err != nil {
  102. return err
  103. }
  104. log.Println("successfully updated store to version " + change.TargetVersion)
  105. }
  106. return nil
  107. })
  108. if err != nil {
  109. log.Fatal("Could not update datastore:", err.Error())
  110. }
  111. return
  112. }
  113. func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
  114. // == version 1 -> 2 ==
  115. // account key changes and account.verified key bugfix.
  116. var keysToRemove []string
  117. newKeys := make(map[string]string)
  118. tx.AscendKeys("account *", func(key, value string) bool {
  119. keysToRemove = append(keysToRemove, key)
  120. splitkey := strings.Split(key, " ")
  121. // work around bug
  122. if splitkey[2] == "exists" {
  123. // manually create new verified key
  124. newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
  125. newKeys[newVerifiedKey] = "1"
  126. } else if splitkey[1] == "%s" {
  127. return true
  128. }
  129. newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
  130. newKeys[newKey] = value
  131. return true
  132. })
  133. for _, key := range keysToRemove {
  134. tx.Delete(key)
  135. }
  136. for key, value := range newKeys {
  137. tx.Set(key, value, nil)
  138. }
  139. return nil
  140. }
  141. // 1. channel founder names should be casefolded
  142. // 2. founder should be explicitly granted the ChannelFounder user mode
  143. // 3. explicitly initialize stored channel modes to the server default values
  144. func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
  145. var channels []string
  146. prefix := "channel.exists "
  147. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  148. if !strings.HasPrefix(key, prefix) {
  149. return false
  150. }
  151. chname := strings.TrimPrefix(key, prefix)
  152. channels = append(channels, chname)
  153. return true
  154. })
  155. // founder names should be casefolded
  156. // founder should be explicitly granted the ChannelFounder user mode
  157. for _, channel := range channels {
  158. founderKey := "channel.founder " + channel
  159. founder, _ := tx.Get(founderKey)
  160. if founder != "" {
  161. founder, err := CasefoldName(founder)
  162. if err == nil {
  163. tx.Set(founderKey, founder, nil)
  164. accountToUmode := map[string]modes.Mode{
  165. founder: modes.ChannelFounder,
  166. }
  167. atustr, _ := json.Marshal(accountToUmode)
  168. tx.Set("channel.accounttoumode "+channel, string(atustr), nil)
  169. }
  170. }
  171. }
  172. // explicitly store the channel modes
  173. defaultModes := ParseDefaultChannelModes(config)
  174. modeStrings := make([]string, len(defaultModes))
  175. for i, mode := range defaultModes {
  176. modeStrings[i] = string(mode)
  177. }
  178. defaultModeString := strings.Join(modeStrings, "")
  179. for _, channel := range channels {
  180. tx.Set("channel.modes "+channel, defaultModeString, nil)
  181. }
  182. return nil
  183. }
  184. func init() {
  185. allChanges := []SchemaChange{
  186. SchemaChange{
  187. InitialVersion: "1",
  188. TargetVersion: "2",
  189. Changer: schemaChangeV1toV2,
  190. },
  191. SchemaChange{
  192. InitialVersion: "2",
  193. TargetVersion: "3",
  194. Changer: schemaChangeV2ToV3,
  195. },
  196. }
  197. // build the index
  198. schemaChanges = make(map[string]SchemaChange)
  199. for _, change := range allChanges {
  200. schemaChanges[change.InitialVersion] = change
  201. }
  202. }