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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "sync"
  6. )
  7. type channelManagerEntry struct {
  8. channel *Channel
  9. // this is a refcount for joins, so we can avoid a race where we incorrectly
  10. // think the channel is empty (without holding a lock across the entire Channel.Join()
  11. // call)
  12. pendingJoins int
  13. skeleton string
  14. }
  15. // ChannelManager keeps track of all the channels on the server,
  16. // providing synchronization for creation of new channels on first join,
  17. // cleanup of empty channels on last part, and renames.
  18. type ChannelManager struct {
  19. sync.RWMutex // tier 2
  20. // chans is the main data structure, mapping casefolded name -> *Channel
  21. chans map[string]*channelManagerEntry
  22. chansSkeletons StringSet // skeletons of *unregistered* chans
  23. registeredChannels StringSet // casefolds of registered chans
  24. registeredSkeletons StringSet // skeletons of registered chans
  25. purgedChannels StringSet // casefolds of purged chans
  26. server *Server
  27. }
  28. // NewChannelManager returns a new ChannelManager.
  29. func (cm *ChannelManager) Initialize(server *Server) {
  30. cm.chans = make(map[string]*channelManagerEntry)
  31. cm.chansSkeletons = make(StringSet)
  32. cm.server = server
  33. cm.loadRegisteredChannels(server.Config())
  34. // purging should work even if registration is disabled
  35. cm.purgedChannels = cm.server.channelRegistry.PurgedChannels()
  36. }
  37. func (cm *ChannelManager) loadRegisteredChannels(config *Config) {
  38. if !config.Channels.Registration.Enabled {
  39. return
  40. }
  41. rawNames := cm.server.channelRegistry.AllChannels()
  42. registeredChannels := make(StringSet, len(rawNames))
  43. registeredSkeletons := make(StringSet, len(rawNames))
  44. for _, name := range rawNames {
  45. cfname, err := CasefoldChannel(name)
  46. if err == nil {
  47. registeredChannels.Add(cfname)
  48. }
  49. skeleton, err := Skeleton(name)
  50. if err == nil {
  51. registeredSkeletons.Add(skeleton)
  52. }
  53. }
  54. cm.Lock()
  55. defer cm.Unlock()
  56. cm.registeredChannels = registeredChannels
  57. cm.registeredSkeletons = registeredSkeletons
  58. }
  59. // Get returns an existing channel with name equivalent to `name`, or nil
  60. func (cm *ChannelManager) Get(name string) (channel *Channel) {
  61. name, err := CasefoldChannel(name)
  62. if err == nil {
  63. cm.RLock()
  64. defer cm.RUnlock()
  65. entry := cm.chans[name]
  66. // if the channel is still loading, pretend we don't have it
  67. if entry != nil && entry.channel.IsLoaded() {
  68. return entry.channel
  69. }
  70. }
  71. return nil
  72. }
  73. // Join causes `client` to join the channel named `name`, creating it if necessary.
  74. func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
  75. server := client.server
  76. casefoldedName, err := CasefoldChannel(name)
  77. skeleton, skerr := Skeleton(name)
  78. if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
  79. return errNoSuchChannel
  80. }
  81. channel, err := func() (*Channel, error) {
  82. cm.Lock()
  83. defer cm.Unlock()
  84. if cm.purgedChannels.Has(casefoldedName) {
  85. return nil, errChannelPurged
  86. }
  87. entry := cm.chans[casefoldedName]
  88. if entry == nil {
  89. registered := cm.registeredChannels.Has(casefoldedName)
  90. // enforce OpOnlyCreation
  91. if !registered && server.Config().Channels.OpOnlyCreation && !client.HasRoleCapabs("chanreg") {
  92. return nil, errInsufficientPrivs
  93. }
  94. // enforce confusables
  95. if cm.chansSkeletons.Has(skeleton) || (!registered && cm.registeredSkeletons.Has(skeleton)) {
  96. return nil, errConfusableIdentifier
  97. }
  98. entry = &channelManagerEntry{
  99. channel: NewChannel(server, name, casefoldedName, registered),
  100. pendingJoins: 0,
  101. }
  102. if !registered {
  103. // for an unregistered channel, we already have the correct unfolded name
  104. // and therefore the final skeleton. for a registered channel, we don't have
  105. // the unfolded name yet (it needs to be loaded from the db), but we already
  106. // have the final skeleton in `registeredSkeletons` so we don't need to track it
  107. cm.chansSkeletons.Add(skeleton)
  108. entry.skeleton = skeleton
  109. }
  110. cm.chans[casefoldedName] = entry
  111. }
  112. entry.pendingJoins += 1
  113. return entry.channel, nil
  114. }()
  115. if err != nil {
  116. return err
  117. }
  118. channel.EnsureLoaded()
  119. channel.Join(client, key, isSajoin, rb)
  120. cm.maybeCleanup(channel, true)
  121. return nil
  122. }
  123. func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
  124. cm.Lock()
  125. defer cm.Unlock()
  126. cfname := channel.NameCasefolded()
  127. entry := cm.chans[cfname]
  128. if entry == nil || entry.channel != channel {
  129. return
  130. }
  131. if afterJoin {
  132. entry.pendingJoins -= 1
  133. }
  134. if entry.pendingJoins == 0 && entry.channel.IsClean() {
  135. delete(cm.chans, cfname)
  136. if entry.skeleton != "" {
  137. delete(cm.chansSkeletons, entry.skeleton)
  138. }
  139. }
  140. }
  141. // Part parts `client` from the channel named `name`, deleting it if it's empty.
  142. func (cm *ChannelManager) Part(client *Client, name string, message string, rb *ResponseBuffer) error {
  143. var channel *Channel
  144. casefoldedName, err := CasefoldChannel(name)
  145. if err != nil {
  146. return errNoSuchChannel
  147. }
  148. cm.RLock()
  149. entry := cm.chans[casefoldedName]
  150. if entry != nil {
  151. channel = entry.channel
  152. }
  153. cm.RUnlock()
  154. if channel == nil {
  155. return errNoSuchChannel
  156. }
  157. channel.Part(client, message, rb)
  158. return nil
  159. }
  160. func (cm *ChannelManager) Cleanup(channel *Channel) {
  161. cm.maybeCleanup(channel, false)
  162. }
  163. func (cm *ChannelManager) SetRegistered(channelName string, account string) (err error) {
  164. var channel *Channel
  165. cfname, err := CasefoldChannel(channelName)
  166. if err != nil {
  167. return err
  168. }
  169. var entry *channelManagerEntry
  170. defer func() {
  171. if err == nil && channel != nil {
  172. // registration was successful: make the database reflect it
  173. err = channel.Store(IncludeAllChannelAttrs)
  174. }
  175. }()
  176. cm.Lock()
  177. defer cm.Unlock()
  178. entry = cm.chans[cfname]
  179. if entry == nil {
  180. return errNoSuchChannel
  181. }
  182. channel = entry.channel
  183. err = channel.SetRegistered(account)
  184. if err != nil {
  185. return err
  186. }
  187. cm.registeredChannels.Add(cfname)
  188. return nil
  189. }
  190. func (cm *ChannelManager) SetUnregistered(channelName string, account string) (err error) {
  191. cfname, err := CasefoldChannel(channelName)
  192. if err != nil {
  193. return err
  194. }
  195. var info RegisteredChannel
  196. defer func() {
  197. if err == nil {
  198. err = cm.server.channelRegistry.Delete(info)
  199. }
  200. }()
  201. cm.Lock()
  202. defer cm.Unlock()
  203. entry := cm.chans[cfname]
  204. if entry == nil {
  205. return errNoSuchChannel
  206. }
  207. info = entry.channel.ExportRegistration(0)
  208. if info.Founder != account {
  209. return errChannelNotOwnedByAccount
  210. }
  211. entry.channel.SetUnregistered(account)
  212. delete(cm.registeredChannels, cfname)
  213. return nil
  214. }
  215. // Rename renames a channel (but does not notify the members)
  216. func (cm *ChannelManager) Rename(name string, newName string) (err error) {
  217. cfname, err := CasefoldChannel(name)
  218. if err != nil {
  219. return errNoSuchChannel
  220. }
  221. newCfname, err := CasefoldChannel(newName)
  222. if err != nil {
  223. return errInvalidChannelName
  224. }
  225. newSkeleton, err := Skeleton(newName)
  226. if err != nil {
  227. return errInvalidChannelName
  228. }
  229. var channel *Channel
  230. var info RegisteredChannel
  231. defer func() {
  232. if channel != nil && info.Founder != "" {
  233. channel.Store(IncludeAllChannelAttrs)
  234. // we just flushed the channel under its new name, therefore this delete
  235. // cannot be overwritten by a write to the old name:
  236. cm.server.channelRegistry.Delete(info)
  237. }
  238. }()
  239. cm.Lock()
  240. defer cm.Unlock()
  241. if cm.chans[newCfname] != nil || cm.registeredChannels.Has(newCfname) {
  242. return errChannelNameInUse
  243. }
  244. if cm.chansSkeletons.Has(newSkeleton) || cm.registeredSkeletons.Has(newSkeleton) {
  245. return errChannelNameInUse
  246. }
  247. entry := cm.chans[cfname]
  248. if entry == nil || !entry.channel.IsLoaded() {
  249. return errNoSuchChannel
  250. }
  251. channel = entry.channel
  252. info = channel.ExportRegistration(IncludeInitial)
  253. registered := info.Founder != ""
  254. delete(cm.chans, cfname)
  255. cm.chans[newCfname] = entry
  256. if registered {
  257. delete(cm.registeredChannels, cfname)
  258. if oldSkeleton, err := Skeleton(info.Name); err == nil {
  259. delete(cm.registeredSkeletons, oldSkeleton)
  260. }
  261. cm.registeredChannels.Add(newCfname)
  262. cm.registeredSkeletons.Add(newSkeleton)
  263. } else {
  264. delete(cm.chansSkeletons, entry.skeleton)
  265. cm.chansSkeletons.Add(newSkeleton)
  266. entry.skeleton = newSkeleton
  267. cm.chans[cfname] = entry
  268. }
  269. entry.channel.Rename(newName, newCfname)
  270. return nil
  271. }
  272. // Len returns the number of channels
  273. func (cm *ChannelManager) Len() int {
  274. cm.RLock()
  275. defer cm.RUnlock()
  276. return len(cm.chans)
  277. }
  278. // Channels returns a slice containing all current channels
  279. func (cm *ChannelManager) Channels() (result []*Channel) {
  280. cm.RLock()
  281. defer cm.RUnlock()
  282. result = make([]*Channel, 0, len(cm.chans))
  283. for _, entry := range cm.chans {
  284. if entry.channel.IsLoaded() {
  285. result = append(result, entry.channel)
  286. }
  287. }
  288. return
  289. }
  290. // Purge marks a channel as purged.
  291. func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err error) {
  292. chname, err = CasefoldChannel(chname)
  293. if err != nil {
  294. return errInvalidChannelName
  295. }
  296. cm.Lock()
  297. cm.purgedChannels.Add(chname)
  298. cm.Unlock()
  299. cm.server.channelRegistry.PurgeChannel(chname, record)
  300. return nil
  301. }
  302. // IsPurged queries whether a channel is purged.
  303. func (cm *ChannelManager) IsPurged(chname string) (result bool) {
  304. chname, err := CasefoldChannel(chname)
  305. if err != nil {
  306. return false
  307. }
  308. cm.Lock()
  309. result = cm.purgedChannels.Has(chname)
  310. cm.Unlock()
  311. return
  312. }
  313. // Unpurge deletes a channel's purged status.
  314. func (cm *ChannelManager) Unpurge(chname string) (err error) {
  315. chname, err = CasefoldChannel(chname)
  316. if err != nil {
  317. return errNoSuchChannel
  318. }
  319. cm.Lock()
  320. found := cm.purgedChannels.Has(chname)
  321. delete(cm.purgedChannels, chname)
  322. cm.Unlock()
  323. cm.server.channelRegistry.UnpurgeChannel(chname)
  324. if !found {
  325. return errNoSuchChannel
  326. }
  327. return nil
  328. }