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.

channelmanager.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "sort"
  6. "sync"
  7. "time"
  8. "github.com/ergochat/ergo/irc/datastore"
  9. "github.com/ergochat/ergo/irc/utils"
  10. )
  11. type channelManagerEntry struct {
  12. channel *Channel
  13. // this is a refcount for joins, so we can avoid a race where we incorrectly
  14. // think the channel is empty (without holding a lock across the entire Channel.Join()
  15. // call)
  16. pendingJoins int
  17. skeleton string
  18. }
  19. // ChannelManager keeps track of all the channels on the server,
  20. // providing synchronization for creation of new channels on first join,
  21. // cleanup of empty channels on last part, and renames.
  22. type ChannelManager struct {
  23. sync.RWMutex // tier 2
  24. // chans is the main data structure, mapping casefolded name -> *Channel
  25. chans map[string]*channelManagerEntry
  26. chansSkeletons utils.HashSet[string]
  27. purgedChannels map[string]ChannelPurgeRecord // casefolded name to purge record
  28. server *Server
  29. }
  30. // NewChannelManager returns a new ChannelManager.
  31. func (cm *ChannelManager) Initialize(server *Server, config *Config) (err error) {
  32. cm.chans = make(map[string]*channelManagerEntry)
  33. cm.chansSkeletons = make(utils.HashSet[string])
  34. cm.server = server
  35. return cm.loadRegisteredChannels(config)
  36. }
  37. func (cm *ChannelManager) loadRegisteredChannels(config *Config) (err error) {
  38. allChannels, err := FetchAndDeserializeAll[RegisteredChannel](datastore.TableChannels, cm.server.dstore, cm.server.logger)
  39. if err != nil {
  40. return
  41. }
  42. allPurgeRecords, err := FetchAndDeserializeAll[ChannelPurgeRecord](datastore.TableChannelPurges, cm.server.dstore, cm.server.logger)
  43. if err != nil {
  44. return
  45. }
  46. cm.Lock()
  47. defer cm.Unlock()
  48. cm.purgedChannels = make(map[string]ChannelPurgeRecord, len(allPurgeRecords))
  49. for _, purge := range allPurgeRecords {
  50. cm.purgedChannels[purge.NameCasefolded] = purge
  51. }
  52. for _, regInfo := range allChannels {
  53. cfname, err := CasefoldChannel(regInfo.Name)
  54. if err != nil {
  55. cm.server.logger.Error("channels", "couldn't casefold registered channel, skipping", regInfo.Name, err.Error())
  56. continue
  57. } else {
  58. cm.server.logger.Debug("channels", "initializing registered channel", regInfo.Name)
  59. }
  60. skeleton, err := Skeleton(regInfo.Name)
  61. if err == nil {
  62. cm.chansSkeletons.Add(skeleton)
  63. }
  64. if _, ok := cm.purgedChannels[cfname]; !ok {
  65. ch := NewChannel(cm.server, regInfo.Name, cfname, true, regInfo)
  66. cm.chans[cfname] = &channelManagerEntry{
  67. channel: ch,
  68. pendingJoins: 0,
  69. skeleton: skeleton,
  70. }
  71. }
  72. }
  73. return nil
  74. }
  75. // Get returns an existing channel with name equivalent to `name`, or nil
  76. func (cm *ChannelManager) Get(name string) (channel *Channel) {
  77. name, err := CasefoldChannel(name)
  78. if err != nil {
  79. return nil
  80. }
  81. cm.RLock()
  82. defer cm.RUnlock()
  83. entry := cm.chans[name]
  84. if entry != nil {
  85. return entry.channel
  86. }
  87. return nil
  88. }
  89. // Join causes `client` to join the channel named `name`, creating it if necessary.
  90. func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) (err error, forward string) {
  91. server := client.server
  92. casefoldedName, err := CasefoldChannel(name)
  93. skeleton, skerr := Skeleton(name)
  94. if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
  95. return errNoSuchChannel, ""
  96. }
  97. channel, err, newChannel := func() (*Channel, error, bool) {
  98. var newChannel bool
  99. cm.Lock()
  100. defer cm.Unlock()
  101. // check purges first; a registered purged channel will still be present in `chans`
  102. if _, ok := cm.purgedChannels[casefoldedName]; ok {
  103. return nil, errChannelPurged, false
  104. }
  105. entry := cm.chans[casefoldedName]
  106. if entry == nil {
  107. if server.Config().Channels.OpOnlyCreation &&
  108. !(isSajoin || client.HasRoleCapabs("chanreg")) {
  109. return nil, errInsufficientPrivs, false
  110. }
  111. // enforce confusables
  112. if cm.chansSkeletons.Has(skeleton) {
  113. return nil, errConfusableIdentifier, false
  114. }
  115. entry = &channelManagerEntry{
  116. channel: NewChannel(server, name, casefoldedName, false, RegisteredChannel{}),
  117. pendingJoins: 0,
  118. }
  119. cm.chansSkeletons.Add(skeleton)
  120. entry.skeleton = skeleton
  121. cm.chans[casefoldedName] = entry
  122. newChannel = true
  123. }
  124. entry.pendingJoins += 1
  125. return entry.channel, nil, newChannel
  126. }()
  127. if err != nil {
  128. return err, ""
  129. }
  130. err, forward = channel.Join(client, key, isSajoin || newChannel, rb)
  131. cm.maybeCleanup(channel, true)
  132. return
  133. }
  134. func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
  135. cm.Lock()
  136. defer cm.Unlock()
  137. cfname := channel.NameCasefolded()
  138. entry := cm.chans[cfname]
  139. if entry == nil || entry.channel != channel {
  140. return
  141. }
  142. cm.maybeCleanupInternal(cfname, entry, afterJoin)
  143. }
  144. func (cm *ChannelManager) maybeCleanupInternal(cfname string, entry *channelManagerEntry, afterJoin bool) {
  145. if afterJoin {
  146. entry.pendingJoins -= 1
  147. }
  148. if entry.pendingJoins == 0 && entry.channel.IsClean() {
  149. delete(cm.chans, cfname)
  150. if entry.skeleton != "" {
  151. delete(cm.chansSkeletons, entry.skeleton)
  152. }
  153. }
  154. }
  155. // Part parts `client` from the channel named `name`, deleting it if it's empty.
  156. func (cm *ChannelManager) Part(client *Client, name string, message string, rb *ResponseBuffer) error {
  157. var channel *Channel
  158. casefoldedName, err := CasefoldChannel(name)
  159. if err != nil {
  160. return errNoSuchChannel
  161. }
  162. cm.RLock()
  163. entry := cm.chans[casefoldedName]
  164. if entry != nil {
  165. channel = entry.channel
  166. }
  167. cm.RUnlock()
  168. if channel == nil {
  169. return errNoSuchChannel
  170. }
  171. channel.Part(client, message, rb)
  172. return nil
  173. }
  174. func (cm *ChannelManager) Cleanup(channel *Channel) {
  175. cm.maybeCleanup(channel, false)
  176. }
  177. func (cm *ChannelManager) SetRegistered(channelName string, account string) (err error) {
  178. if cm.server.Defcon() <= 4 {
  179. return errFeatureDisabled
  180. }
  181. var channel *Channel
  182. cfname, err := CasefoldChannel(channelName)
  183. if err != nil {
  184. return err
  185. }
  186. var entry *channelManagerEntry
  187. defer func() {
  188. if err == nil && channel != nil {
  189. // registration was successful: make the database reflect it
  190. err = channel.Store(IncludeAllAttrs)
  191. }
  192. }()
  193. cm.Lock()
  194. defer cm.Unlock()
  195. entry = cm.chans[cfname]
  196. if entry == nil {
  197. return errNoSuchChannel
  198. }
  199. channel = entry.channel
  200. err = channel.SetRegistered(account)
  201. if err != nil {
  202. return err
  203. }
  204. return nil
  205. }
  206. func (cm *ChannelManager) SetUnregistered(channelName string, account string) (err error) {
  207. cfname, err := CasefoldChannel(channelName)
  208. if err != nil {
  209. return err
  210. }
  211. var uuid utils.UUID
  212. defer func() {
  213. if err == nil {
  214. if delErr := cm.server.dstore.Delete(datastore.TableChannels, uuid); delErr != nil {
  215. cm.server.logger.Error("datastore", "couldn't delete channel registration", cfname, delErr.Error())
  216. }
  217. }
  218. }()
  219. cm.Lock()
  220. defer cm.Unlock()
  221. entry := cm.chans[cfname]
  222. if entry != nil {
  223. if entry.channel.Founder() != account {
  224. return errChannelNotOwnedByAccount
  225. }
  226. uuid = entry.channel.UUID()
  227. entry.channel.SetUnregistered(account) // changes the UUID
  228. // #1619: if the channel has 0 members and was only being retained
  229. // because it was registered, clean it up:
  230. cm.maybeCleanupInternal(cfname, entry, false)
  231. }
  232. return nil
  233. }
  234. // Rename renames a channel (but does not notify the members)
  235. func (cm *ChannelManager) Rename(name string, newName string) (err error) {
  236. oldCfname, err := CasefoldChannel(name)
  237. if err != nil {
  238. return errNoSuchChannel
  239. }
  240. newCfname, err := CasefoldChannel(newName)
  241. if err != nil {
  242. return errInvalidChannelName
  243. }
  244. newSkeleton, err := Skeleton(newName)
  245. if err != nil {
  246. return errInvalidChannelName
  247. }
  248. var channel *Channel
  249. var info RegisteredChannel
  250. defer func() {
  251. if channel != nil && info.Founder != "" {
  252. channel.MarkDirty(IncludeAllAttrs)
  253. }
  254. // always-on clients need to update their saved channel memberships
  255. for _, member := range channel.Members() {
  256. member.markDirty(IncludeChannels)
  257. }
  258. }()
  259. cm.Lock()
  260. defer cm.Unlock()
  261. entry := cm.chans[oldCfname]
  262. if entry == nil {
  263. return errNoSuchChannel
  264. }
  265. channel = entry.channel
  266. info = channel.ExportRegistration()
  267. registered := info.Founder != ""
  268. oldSkeleton, err := Skeleton(info.Name)
  269. if err != nil {
  270. return errNoSuchChannel // ugh
  271. }
  272. if newCfname != oldCfname {
  273. if cm.chans[newCfname] != nil {
  274. return errChannelNameInUse
  275. }
  276. }
  277. if oldSkeleton != newSkeleton {
  278. if cm.chansSkeletons.Has(newSkeleton) {
  279. return errConfusableIdentifier
  280. }
  281. }
  282. delete(cm.chans, oldCfname)
  283. if !registered {
  284. entry.skeleton = newSkeleton
  285. }
  286. cm.chans[newCfname] = entry
  287. delete(cm.chansSkeletons, oldSkeleton)
  288. cm.chansSkeletons.Add(newSkeleton)
  289. entry.channel.Rename(newName, newCfname)
  290. return nil
  291. }
  292. // Len returns the number of channels
  293. func (cm *ChannelManager) Len() int {
  294. cm.RLock()
  295. defer cm.RUnlock()
  296. return len(cm.chans)
  297. }
  298. // Channels returns a slice containing all current channels
  299. func (cm *ChannelManager) Channels() (result []*Channel) {
  300. cm.RLock()
  301. defer cm.RUnlock()
  302. result = make([]*Channel, 0, len(cm.chans))
  303. for _, entry := range cm.chans {
  304. result = append(result, entry.channel)
  305. }
  306. return
  307. }
  308. // ListableChannels returns a slice of all non-purged channels.
  309. func (cm *ChannelManager) ListableChannels() (result []*Channel) {
  310. cm.RLock()
  311. defer cm.RUnlock()
  312. result = make([]*Channel, 0, len(cm.chans))
  313. for cfname, entry := range cm.chans {
  314. if _, ok := cm.purgedChannels[cfname]; !ok {
  315. result = append(result, entry.channel)
  316. }
  317. }
  318. return
  319. }
  320. // Purge marks a channel as purged.
  321. func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err error) {
  322. chname, err = CasefoldChannel(chname)
  323. if err != nil {
  324. return errInvalidChannelName
  325. }
  326. record.NameCasefolded = chname
  327. record.UUID = utils.GenerateUUIDv4()
  328. channel, err := func() (channel *Channel, err error) {
  329. cm.Lock()
  330. defer cm.Unlock()
  331. if _, ok := cm.purgedChannels[chname]; ok {
  332. return nil, errChannelPurgedAlready
  333. }
  334. entry := cm.chans[chname]
  335. // atomically prevent anyone from rejoining
  336. cm.purgedChannels[chname] = record
  337. if entry != nil {
  338. channel = entry.channel
  339. }
  340. return
  341. }()
  342. if err != nil {
  343. return err
  344. }
  345. if channel != nil {
  346. // actually kick everyone off the channel
  347. channel.Purge("")
  348. }
  349. var purgeBytes []byte
  350. if purgeBytes, err = record.Serialize(); err != nil {
  351. cm.server.logger.Error("internal", "couldn't serialize purge record", channel.Name(), err.Error())
  352. }
  353. // TODO we need a better story about error handling for later
  354. if err = cm.server.dstore.Set(datastore.TableChannelPurges, record.UUID, purgeBytes, time.Time{}); err != nil {
  355. cm.server.logger.Error("datastore", "couldn't store purge record", chname, err.Error())
  356. }
  357. return
  358. }
  359. // IsPurged queries whether a channel is purged.
  360. func (cm *ChannelManager) IsPurged(chname string) (result bool) {
  361. chname, err := CasefoldChannel(chname)
  362. if err != nil {
  363. return false
  364. }
  365. cm.RLock()
  366. _, result = cm.purgedChannels[chname]
  367. cm.RUnlock()
  368. return
  369. }
  370. // Unpurge deletes a channel's purged status.
  371. func (cm *ChannelManager) Unpurge(chname string) (err error) {
  372. chname, err = CasefoldChannel(chname)
  373. if err != nil {
  374. return errNoSuchChannel
  375. }
  376. cm.Lock()
  377. record, found := cm.purgedChannels[chname]
  378. delete(cm.purgedChannels, chname)
  379. cm.Unlock()
  380. if !found {
  381. return errNoSuchChannel
  382. }
  383. if err := cm.server.dstore.Delete(datastore.TableChannelPurges, record.UUID); err != nil {
  384. cm.server.logger.Error("datastore", "couldn't delete purge record", chname, err.Error())
  385. }
  386. return nil
  387. }
  388. func (cm *ChannelManager) ListPurged() (result []string) {
  389. cm.RLock()
  390. result = make([]string, 0, len(cm.purgedChannels))
  391. for c := range cm.purgedChannels {
  392. result = append(result, c)
  393. }
  394. cm.RUnlock()
  395. sort.Strings(result)
  396. return
  397. }
  398. func (cm *ChannelManager) UnfoldName(cfname string) (result string) {
  399. cm.RLock()
  400. entry := cm.chans[cfname]
  401. cm.RUnlock()
  402. if entry != nil {
  403. return entry.channel.Name()
  404. }
  405. return cfname
  406. }
  407. func (cm *ChannelManager) LoadPurgeRecord(cfchname string) (record ChannelPurgeRecord, err error) {
  408. cm.RLock()
  409. defer cm.RUnlock()
  410. if record, ok := cm.purgedChannels[cfchname]; ok {
  411. return record, nil
  412. } else {
  413. return record, errNoSuchChannel
  414. }
  415. }
  416. func (cm *ChannelManager) ChannelsForAccount(account string) (channels []string) {
  417. cm.RLock()
  418. defer cm.RUnlock()
  419. for cfname, entry := range cm.chans {
  420. if entry.channel.Founder() == account {
  421. channels = append(channels, cfname)
  422. }
  423. }
  424. return
  425. }
  426. // AllChannels returns the uncasefolded names of all registered channels.
  427. func (cm *ChannelManager) AllRegisteredChannels() (result []string) {
  428. cm.RLock()
  429. defer cm.RUnlock()
  430. for cfname, entry := range cm.chans {
  431. if entry.channel.Founder() != "" {
  432. result = append(result, cfname)
  433. }
  434. }
  435. return
  436. }