1// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2// released under the MIT license
3
4package irc
5
6import (
7	"encoding/json"
8	"fmt"
9	"strconv"
10	"strings"
11	"time"
12
13	"github.com/tidwall/buntdb"
14
15	"github.com/ergochat/ergo/irc/modes"
16	"github.com/ergochat/ergo/irc/utils"
17)
18
19// this is exclusively the *persistence* layer for channel registration;
20// channel creation/tracking/destruction is in channelmanager.go
21
22const (
23	keyChannelExists         = "channel.exists %s"
24	keyChannelName           = "channel.name %s" // stores the 'preferred name' of the channel, not casemapped
25	keyChannelRegTime        = "channel.registered.time %s"
26	keyChannelFounder        = "channel.founder %s"
27	keyChannelTopic          = "channel.topic %s"
28	keyChannelTopicSetBy     = "channel.topic.setby %s"
29	keyChannelTopicSetTime   = "channel.topic.settime %s"
30	keyChannelBanlist        = "channel.banlist %s"
31	keyChannelExceptlist     = "channel.exceptlist %s"
32	keyChannelInvitelist     = "channel.invitelist %s"
33	keyChannelPassword       = "channel.key %s"
34	keyChannelModes          = "channel.modes %s"
35	keyChannelAccountToUMode = "channel.accounttoumode %s"
36	keyChannelUserLimit      = "channel.userlimit %s"
37	keyChannelSettings       = "channel.settings %s"
38	keyChannelForward        = "channel.forward %s"
39
40	keyChannelPurged = "channel.purged %s"
41)
42
43var (
44	channelKeyStrings = []string{
45		keyChannelExists,
46		keyChannelName,
47		keyChannelRegTime,
48		keyChannelFounder,
49		keyChannelTopic,
50		keyChannelTopicSetBy,
51		keyChannelTopicSetTime,
52		keyChannelBanlist,
53		keyChannelExceptlist,
54		keyChannelInvitelist,
55		keyChannelPassword,
56		keyChannelModes,
57		keyChannelAccountToUMode,
58		keyChannelUserLimit,
59		keyChannelSettings,
60		keyChannelForward,
61	}
62)
63
64// these are bit flags indicating what part of the channel status is "dirty"
65// and needs to be read from memory and written to the db
66const (
67	IncludeInitial uint = 1 << iota
68	IncludeTopic
69	IncludeModes
70	IncludeLists
71	IncludeSettings
72)
73
74// this is an OR of all possible flags
75const (
76	IncludeAllAttrs = ^uint(0)
77)
78
79// RegisteredChannel holds details about a given registered channel.
80type RegisteredChannel struct {
81	// Name of the channel.
82	Name string
83	// Casefolded name of the channel.
84	NameCasefolded string
85	// RegisteredAt represents the time that the channel was registered.
86	RegisteredAt time.Time
87	// Founder indicates the founder of the channel.
88	Founder string
89	// Topic represents the channel topic.
90	Topic string
91	// TopicSetBy represents the host that set the topic.
92	TopicSetBy string
93	// TopicSetTime represents the time the topic was set.
94	TopicSetTime time.Time
95	// Modes represents the channel modes
96	Modes []modes.Mode
97	// Key represents the channel key / password
98	Key string
99	// Forward is the forwarding/overflow (+f) channel
100	Forward string
101	// UserLimit is the user limit (0 for no limit)
102	UserLimit int
103	// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h)
104	AccountToUMode map[string]modes.Mode
105	// Bans represents the bans set on the channel.
106	Bans map[string]MaskInfo
107	// Excepts represents the exceptions set on the channel.
108	Excepts map[string]MaskInfo
109	// Invites represents the invite exceptions set on the channel.
110	Invites map[string]MaskInfo
111	// Settings are the chanserv-modifiable settings
112	Settings ChannelSettings
113}
114
115type ChannelPurgeRecord struct {
116	Oper     string
117	PurgedAt time.Time
118	Reason   string
119}
120
121// ChannelRegistry manages registered channels.
122type ChannelRegistry struct {
123	server *Server
124}
125
126// NewChannelRegistry returns a new ChannelRegistry.
127func (reg *ChannelRegistry) Initialize(server *Server) {
128	reg.server = server
129}
130
131// AllChannels returns the uncasefolded names of all registered channels.
132func (reg *ChannelRegistry) AllChannels() (result []string) {
133	prefix := fmt.Sprintf(keyChannelName, "")
134	reg.server.store.View(func(tx *buntdb.Tx) error {
135		return tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
136			if !strings.HasPrefix(key, prefix) {
137				return false
138			}
139			result = append(result, value)
140			return true
141		})
142	})
143
144	return
145}
146
147// PurgedChannels returns the set of all casefolded channel names that have been purged
148func (reg *ChannelRegistry) PurgedChannels() (result utils.StringSet) {
149	result = make(utils.StringSet)
150
151	prefix := fmt.Sprintf(keyChannelPurged, "")
152	reg.server.store.View(func(tx *buntdb.Tx) error {
153		return tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
154			if !strings.HasPrefix(key, prefix) {
155				return false
156			}
157			channel := strings.TrimPrefix(key, prefix)
158			result.Add(channel)
159			return true
160		})
161	})
162	return
163}
164
165// StoreChannel obtains a consistent view of a channel, then persists it to the store.
166func (reg *ChannelRegistry) StoreChannel(info RegisteredChannel, includeFlags uint) (err error) {
167	if !reg.server.ChannelRegistrationEnabled() {
168		return
169	}
170
171	if info.Founder == "" {
172		// sanity check, don't try to store an unregistered channel
173		return
174	}
175
176	reg.server.store.Update(func(tx *buntdb.Tx) error {
177		reg.saveChannel(tx, info, includeFlags)
178		return nil
179	})
180
181	return nil
182}
183
184// LoadChannel loads a channel from the store.
185func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredChannel, err error) {
186	if !reg.server.ChannelRegistrationEnabled() {
187		err = errFeatureDisabled
188		return
189	}
190
191	channelKey := nameCasefolded
192	// nice to have: do all JSON (de)serialization outside of the buntdb transaction
193	err = reg.server.store.View(func(tx *buntdb.Tx) error {
194		_, dberr := tx.Get(fmt.Sprintf(keyChannelExists, channelKey))
195		if dberr == buntdb.ErrNotFound {
196			// chan does not already exist, return
197			return errNoSuchChannel
198		}
199
200		// channel exists, load it
201		name, _ := tx.Get(fmt.Sprintf(keyChannelName, channelKey))
202		regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, channelKey))
203		regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
204		founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, channelKey))
205		topic, _ := tx.Get(fmt.Sprintf(keyChannelTopic, channelKey))
206		topicSetBy, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetBy, channelKey))
207		var topicSetTime time.Time
208		topicSetTimeStr, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetTime, channelKey))
209		if topicSetTimeInt, topicSetTimeErr := strconv.ParseInt(topicSetTimeStr, 10, 64); topicSetTimeErr == nil {
210			topicSetTime = time.Unix(0, topicSetTimeInt).UTC()
211		}
212		password, _ := tx.Get(fmt.Sprintf(keyChannelPassword, channelKey))
213		modeString, _ := tx.Get(fmt.Sprintf(keyChannelModes, channelKey))
214		userLimitString, _ := tx.Get(fmt.Sprintf(keyChannelUserLimit, channelKey))
215		forward, _ := tx.Get(fmt.Sprintf(keyChannelForward, channelKey))
216		banlistString, _ := tx.Get(fmt.Sprintf(keyChannelBanlist, channelKey))
217		exceptlistString, _ := tx.Get(fmt.Sprintf(keyChannelExceptlist, channelKey))
218		invitelistString, _ := tx.Get(fmt.Sprintf(keyChannelInvitelist, channelKey))
219		accountToUModeString, _ := tx.Get(fmt.Sprintf(keyChannelAccountToUMode, channelKey))
220		settingsString, _ := tx.Get(fmt.Sprintf(keyChannelSettings, channelKey))
221
222		modeSlice := make([]modes.Mode, len(modeString))
223		for i, mode := range modeString {
224			modeSlice[i] = modes.Mode(mode)
225		}
226
227		userLimit, _ := strconv.Atoi(userLimitString)
228
229		var banlist map[string]MaskInfo
230		_ = json.Unmarshal([]byte(banlistString), &banlist)
231		var exceptlist map[string]MaskInfo
232		_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
233		var invitelist map[string]MaskInfo
234		_ = json.Unmarshal([]byte(invitelistString), &invitelist)
235		accountToUMode := make(map[string]modes.Mode)
236		_ = json.Unmarshal([]byte(accountToUModeString), &accountToUMode)
237
238		var settings ChannelSettings
239		_ = json.Unmarshal([]byte(settingsString), &settings)
240
241		info = RegisteredChannel{
242			Name:           name,
243			NameCasefolded: nameCasefolded,
244			RegisteredAt:   time.Unix(0, regTimeInt).UTC(),
245			Founder:        founder,
246			Topic:          topic,
247			TopicSetBy:     topicSetBy,
248			TopicSetTime:   topicSetTime,
249			Key:            password,
250			Modes:          modeSlice,
251			Bans:           banlist,
252			Excepts:        exceptlist,
253			Invites:        invitelist,
254			AccountToUMode: accountToUMode,
255			UserLimit:      int(userLimit),
256			Settings:       settings,
257			Forward:        forward,
258		}
259		return nil
260	})
261
262	return
263}
264
265// Delete deletes a channel corresponding to `info`. If no such channel
266// is present in the database, no error is returned.
267func (reg *ChannelRegistry) Delete(info RegisteredChannel) (err error) {
268	if !reg.server.ChannelRegistrationEnabled() {
269		return
270	}
271
272	reg.server.store.Update(func(tx *buntdb.Tx) error {
273		reg.deleteChannel(tx, info.NameCasefolded, info)
274		return nil
275	})
276	return nil
277}
278
279// delete a channel, unless it was overwritten by another registration of the same channel
280func (reg *ChannelRegistry) deleteChannel(tx *buntdb.Tx, key string, info RegisteredChannel) {
281	_, err := tx.Get(fmt.Sprintf(keyChannelExists, key))
282	if err == nil {
283		regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, key))
284		regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
285		registeredAt := time.Unix(0, regTimeInt).UTC()
286		founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, key))
287
288		// to see if we're deleting the right channel, confirm the founder and the registration time
289		if founder == info.Founder && registeredAt.Equal(info.RegisteredAt) {
290			for _, keyFmt := range channelKeyStrings {
291				tx.Delete(fmt.Sprintf(keyFmt, key))
292			}
293
294			// remove this channel from the client's list of registered channels
295			channelsKey := fmt.Sprintf(keyAccountChannels, info.Founder)
296			channelsStr, err := tx.Get(channelsKey)
297			if err == buntdb.ErrNotFound {
298				return
299			}
300			registeredChannels := unmarshalRegisteredChannels(channelsStr)
301			var nowRegisteredChannels []string
302			for _, channel := range registeredChannels {
303				if channel != key {
304					nowRegisteredChannels = append(nowRegisteredChannels, channel)
305				}
306			}
307			tx.Set(channelsKey, strings.Join(nowRegisteredChannels, ","), nil)
308		}
309	}
310}
311
312func (reg *ChannelRegistry) updateAccountToChannelMapping(tx *buntdb.Tx, channelInfo RegisteredChannel) {
313	channelKey := channelInfo.NameCasefolded
314	chanFounderKey := fmt.Sprintf(keyChannelFounder, channelKey)
315	founder, existsErr := tx.Get(chanFounderKey)
316	if existsErr == buntdb.ErrNotFound || founder != channelInfo.Founder {
317		// add to new founder's list
318		accountChannelsKey := fmt.Sprintf(keyAccountChannels, channelInfo.Founder)
319		alreadyChannels, _ := tx.Get(accountChannelsKey)
320		newChannels := channelKey // this is the casefolded channel name
321		if alreadyChannels != "" {
322			newChannels = fmt.Sprintf("%s,%s", alreadyChannels, newChannels)
323		}
324		tx.Set(accountChannelsKey, newChannels, nil)
325	}
326	if existsErr == nil && founder != channelInfo.Founder {
327		// remove from old founder's list
328		accountChannelsKey := fmt.Sprintf(keyAccountChannels, founder)
329		alreadyChannelsRaw, _ := tx.Get(accountChannelsKey)
330		var newChannels []string
331		if alreadyChannelsRaw != "" {
332			for _, chname := range strings.Split(alreadyChannelsRaw, ",") {
333				if chname != channelInfo.NameCasefolded {
334					newChannels = append(newChannels, chname)
335				}
336			}
337		}
338		tx.Set(accountChannelsKey, strings.Join(newChannels, ","), nil)
339	}
340}
341
342// saveChannel saves a channel to the store.
343func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredChannel, includeFlags uint) {
344	channelKey := channelInfo.NameCasefolded
345	// maintain the mapping of account -> registered channels
346	reg.updateAccountToChannelMapping(tx, channelInfo)
347
348	if includeFlags&IncludeInitial != 0 {
349		tx.Set(fmt.Sprintf(keyChannelExists, channelKey), "1", nil)
350		tx.Set(fmt.Sprintf(keyChannelName, channelKey), channelInfo.Name, nil)
351		tx.Set(fmt.Sprintf(keyChannelRegTime, channelKey), strconv.FormatInt(channelInfo.RegisteredAt.UnixNano(), 10), nil)
352		tx.Set(fmt.Sprintf(keyChannelFounder, channelKey), channelInfo.Founder, nil)
353	}
354
355	if includeFlags&IncludeTopic != 0 {
356		tx.Set(fmt.Sprintf(keyChannelTopic, channelKey), channelInfo.Topic, nil)
357		var topicSetTimeStr string
358		if !channelInfo.TopicSetTime.IsZero() {
359			topicSetTimeStr = strconv.FormatInt(channelInfo.TopicSetTime.UnixNano(), 10)
360		}
361		tx.Set(fmt.Sprintf(keyChannelTopicSetTime, channelKey), topicSetTimeStr, nil)
362		tx.Set(fmt.Sprintf(keyChannelTopicSetBy, channelKey), channelInfo.TopicSetBy, nil)
363	}
364
365	if includeFlags&IncludeModes != 0 {
366		tx.Set(fmt.Sprintf(keyChannelPassword, channelKey), channelInfo.Key, nil)
367		modeString := modes.Modes(channelInfo.Modes).String()
368		tx.Set(fmt.Sprintf(keyChannelModes, channelKey), modeString, nil)
369		tx.Set(fmt.Sprintf(keyChannelUserLimit, channelKey), strconv.Itoa(channelInfo.UserLimit), nil)
370		tx.Set(fmt.Sprintf(keyChannelForward, channelKey), channelInfo.Forward, nil)
371	}
372
373	if includeFlags&IncludeLists != 0 {
374		banlistString, _ := json.Marshal(channelInfo.Bans)
375		tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
376		exceptlistString, _ := json.Marshal(channelInfo.Excepts)
377		tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
378		invitelistString, _ := json.Marshal(channelInfo.Invites)
379		tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
380		accountToUModeString, _ := json.Marshal(channelInfo.AccountToUMode)
381		tx.Set(fmt.Sprintf(keyChannelAccountToUMode, channelKey), string(accountToUModeString), nil)
382	}
383
384	if includeFlags&IncludeSettings != 0 {
385		settingsString, _ := json.Marshal(channelInfo.Settings)
386		tx.Set(fmt.Sprintf(keyChannelSettings, channelKey), string(settingsString), nil)
387	}
388}
389
390// PurgeChannel records a channel purge.
391func (reg *ChannelRegistry) PurgeChannel(chname string, record ChannelPurgeRecord) (err error) {
392	serialized, err := json.Marshal(record)
393	if err != nil {
394		return err
395	}
396	serializedStr := string(serialized)
397	key := fmt.Sprintf(keyChannelPurged, chname)
398
399	return reg.server.store.Update(func(tx *buntdb.Tx) error {
400		tx.Set(key, serializedStr, nil)
401		return nil
402	})
403}
404
405// LoadPurgeRecord retrieves information about whether and how a channel was purged.
406func (reg *ChannelRegistry) LoadPurgeRecord(chname string) (record ChannelPurgeRecord, err error) {
407	var rawRecord string
408	key := fmt.Sprintf(keyChannelPurged, chname)
409	reg.server.store.View(func(tx *buntdb.Tx) error {
410		rawRecord, _ = tx.Get(key)
411		return nil
412	})
413	if rawRecord == "" {
414		err = errNoSuchChannel
415		return
416	}
417	err = json.Unmarshal([]byte(rawRecord), &record)
418	if err != nil {
419		reg.server.logger.Error("internal", "corrupt purge record", chname, err.Error())
420		err = errNoSuchChannel
421		return
422	}
423	return
424}
425
426// UnpurgeChannel deletes the record of a channel purge.
427func (reg *ChannelRegistry) UnpurgeChannel(chname string) (err error) {
428	key := fmt.Sprintf(keyChannelPurged, chname)
429	return reg.server.store.Update(func(tx *buntdb.Tx) error {
430		tx.Delete(key)
431		return nil
432	})
433}
434