Parcourir la source

Add very initial ChanServ and NickServ virtual clients

As well, add channel registration and re-applying founder privs on the first client joining the channel. I'm going to re-architect our modes system to better acocunt for this sort of change.
tags/v0.7.0
Daniel Oaks il y a 7 ans
Parent
révision
b33b217fab
10 fichiers modifiés avec 343 ajouts et 31 suppressions
  1. 0
    9
      irc/accountreg.go
  2. 9
    0
      irc/accounts.go
  3. 22
    4
      irc/channel.go
  4. 90
    0
      irc/channelreg.go
  5. 119
    0
      irc/chanserv.go
  6. 16
    0
      irc/commands.go
  7. 20
    0
      irc/help.go
  8. 10
    2
      irc/nickname.go
  9. 24
    0
      irc/nickserv.go
  10. 33
    16
      irc/server.go

irc/registration.go → irc/accountreg.go Voir le fichier

@@ -16,15 +16,6 @@ import (
16 16
 	"github.com/tidwall/buntdb"
17 17
 )
18 18
 
19
-const (
20
-	keyAccountExists      = "account.exists %s"
21
-	keyAccountVerified    = "account.verified %s"
22
-	keyAccountName        = "account.name %s" // stores the 'preferred name' of the account, not casemapped
23
-	keyAccountRegTime     = "account.registered.time %s"
24
-	keyAccountCredentials = "account.credentials %s"
25
-	keyCertToAccount      = "account.creds.certfp %s"
26
-)
27
-
28 19
 var (
29 20
 	errAccountCreation     = errors.New("Account could not be created")
30 21
 	errCertfpAlreadyExists = errors.New("An account already exists with your certificate")

+ 9
- 0
irc/accounts.go Voir le fichier

@@ -17,6 +17,15 @@ import (
17 17
 	"github.com/tidwall/buntdb"
18 18
 )
19 19
 
20
+const (
21
+	keyAccountExists      = "account.exists %s"
22
+	keyAccountVerified    = "account.verified %s"
23
+	keyAccountName        = "account.name %s" // stores the 'preferred name' of the account, not casemapped
24
+	keyAccountRegTime     = "account.registered.time %s"
25
+	keyAccountCredentials = "account.credentials %s"
26
+	keyCertToAccount      = "account.creds.certfp %s"
27
+)
28
+
20 29
 var (
21 30
 	// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
22 31
 	// This can be moved to some other data structure/place if we need to load/unload mechs later.

+ 22
- 4
irc/channel.go Voir le fichier

@@ -14,6 +14,7 @@ import (
14 14
 	"sync"
15 15
 
16 16
 	"github.com/DanielOaks/girc-go/ircmsg"
17
+	"github.com/tidwall/buntdb"
17 18
 )
18 19
 
19 20
 type Channel struct {
@@ -277,10 +278,27 @@ func (channel *Channel) Join(client *Client, key string) {
277 278
 	client.channels.Add(channel)
278 279
 	channel.members.Add(client)
279 280
 	if len(channel.members) == 1 {
280
-		channel.createdTime = time.Now()
281
-		// // we should only do this on registered channels
282
-		// channel.members[client][ChannelFounder] = true
283
-		channel.members[client][ChannelOperator] = true
281
+		client.server.registeredChannelsMutex.Lock()
282
+		defer client.server.registeredChannelsMutex.Unlock()
283
+		client.server.store.Update(func(tx *buntdb.Tx) error {
284
+			chanReg := client.server.loadChannelNoMutex(tx, channel.nameCasefolded)
285
+
286
+			if chanReg == nil {
287
+				channel.createdTime = time.Now()
288
+				channel.members[client][ChannelOperator] = true
289
+			} else {
290
+				// we should only do this on registered channels
291
+				if client.account != nil && client.account.Name == chanReg.Founder {
292
+					channel.members[client][ChannelFounder] = true
293
+				}
294
+				channel.topic = chanReg.Topic
295
+				channel.topicSetBy = chanReg.TopicSetBy
296
+				channel.topicSetTime = chanReg.TopicSetTime
297
+				channel.name = chanReg.Name
298
+				channel.createdTime = chanReg.RegisteredAt
299
+			}
300
+			return nil
301
+		})
284 302
 	}
285 303
 
286 304
 	if client.capabilities[ExtendedJoin] {

+ 90
- 0
irc/channelreg.go Voir le fichier

@@ -0,0 +1,90 @@
1
+// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"errors"
8
+	"fmt"
9
+	"strconv"
10
+	"time"
11
+
12
+	"github.com/tidwall/buntdb"
13
+)
14
+
15
+const (
16
+	keyChannelExists       = "channel.exists %s"
17
+	keyChannelName         = "channel.name %s" // stores the 'preferred name' of the channel, not casemapped
18
+	keyChannelRegTime      = "channel.registered.time %s"
19
+	keyChannelFounder      = "channel.founder %s"
20
+	keyChannelTopic        = "channel.topic %s"
21
+	keyChannelTopicSetBy   = "channel.topic.setby %s"
22
+	keyChannelTopicSetTime = "channel.topic.settime %s"
23
+)
24
+
25
+var (
26
+	errChanExists = errors.New("Channel already exists")
27
+)
28
+
29
+// RegisteredChannel holds details about a given registered channel.
30
+type RegisteredChannel struct {
31
+	// Name of the channel.
32
+	Name string
33
+	// RegisteredAt represents the time that the channel was registered.
34
+	RegisteredAt time.Time
35
+	// Founder indicates the founder of the channel.
36
+	Founder string
37
+	// Topic represents the channel topic.
38
+	Topic string
39
+	// TopicSetBy represents the host that set the topic.
40
+	TopicSetBy string
41
+	// TopicSetTime represents the time the topic was set.
42
+	TopicSetTime time.Time
43
+}
44
+
45
+// loadChannelNoMutex loads a channel from the store.
46
+func (server *Server) loadChannelNoMutex(tx *buntdb.Tx, channelKey string) *RegisteredChannel {
47
+	// return loaded chan if it already exists
48
+	if server.registeredChannels[channelKey] != nil {
49
+		return server.registeredChannels[channelKey]
50
+	}
51
+	_, err := tx.Get(fmt.Sprintf(keyChannelExists, channelKey))
52
+	if err == buntdb.ErrNotFound {
53
+		// chan does not already exist, return
54
+		return nil
55
+	}
56
+
57
+	// channel exists, load it
58
+	name, _ := tx.Get(fmt.Sprintf(keyChannelName, channelKey))
59
+	regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, channelKey))
60
+	regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
61
+	founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, channelKey))
62
+	topic, _ := tx.Get(fmt.Sprintf(keyChannelTopic, channelKey))
63
+	topicSetBy, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetBy, channelKey))
64
+	topicSetTime, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetTime, channelKey))
65
+	topicSetTimeInt, _ := strconv.ParseInt(topicSetTime, 10, 64)
66
+
67
+	chanInfo := RegisteredChannel{
68
+		Name:         name,
69
+		RegisteredAt: time.Unix(regTimeInt, 0),
70
+		Founder:      founder,
71
+		Topic:        topic,
72
+		TopicSetBy:   topicSetBy,
73
+		TopicSetTime: time.Unix(topicSetTimeInt, 0),
74
+	}
75
+	server.registeredChannels[channelKey] = &chanInfo
76
+
77
+	return &chanInfo
78
+}
79
+
80
+// saveChannelNoMutex saves a channel to the store.
81
+func (server *Server) saveChannelNoMutex(tx *buntdb.Tx, channelKey string, channelInfo RegisteredChannel) {
82
+	tx.Set(fmt.Sprintf(keyChannelExists, channelKey), "1", nil)
83
+	tx.Set(fmt.Sprintf(keyChannelName, channelKey), channelInfo.Name, nil)
84
+	tx.Set(fmt.Sprintf(keyChannelRegTime, channelKey), strconv.FormatInt(channelInfo.RegisteredAt.Unix(), 10), nil)
85
+	tx.Set(fmt.Sprintf(keyChannelFounder, channelKey), channelInfo.Founder, nil)
86
+	tx.Set(fmt.Sprintf(keyChannelTopic, channelKey), channelInfo.Topic, nil)
87
+	tx.Set(fmt.Sprintf(keyChannelTopicSetBy, channelKey), channelInfo.TopicSetBy, nil)
88
+	tx.Set(fmt.Sprintf(keyChannelTopicSetTime, channelKey), strconv.FormatInt(channelInfo.TopicSetTime.Unix(), 10), nil)
89
+	server.registeredChannels[channelKey] = &channelInfo
90
+}

+ 119
- 0
irc/chanserv.go Voir le fichier

@@ -0,0 +1,119 @@
1
+// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"fmt"
8
+	"strings"
9
+	"time"
10
+
11
+	"github.com/DanielOaks/girc-go/ircmsg"
12
+	"github.com/tidwall/buntdb"
13
+)
14
+
15
+// csHandler handles the /CS and /CHANSERV commands
16
+func csHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
17
+	server.chanservReceivePrivmsg(client, strings.Join(msg.Params, " "))
18
+	return false
19
+}
20
+
21
+func (server *Server) chanservReceiveNotice(client *Client, message string) {
22
+	// do nothing
23
+}
24
+
25
+// ChanServNotice sends the client a notice from ChanServ.
26
+func (client *Client) ChanServNotice(text string) {
27
+	client.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "NOTICE", client.nick, text)
28
+}
29
+
30
+func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
31
+	var params []string
32
+	for _, p := range strings.Split(message, " ") {
33
+		if len(p) > 0 {
34
+			params = append(params, p)
35
+		}
36
+	}
37
+	if len(params) < 1 {
38
+		client.ChanServNotice("You need to run a command")
39
+		//TODO(dan): dump CS help here
40
+		return
41
+	}
42
+
43
+	command := strings.ToLower(params[0])
44
+	server.logger.Debug("chanserv", fmt.Sprintf("Client %s ran command %s", client.nick, command))
45
+
46
+	if command == "register" {
47
+		if len(params) < 2 {
48
+			client.ChanServNotice("Syntax: REGISTER <channel>")
49
+			return
50
+		}
51
+
52
+		server.registeredChannelsMutex.Lock()
53
+		defer server.registeredChannelsMutex.Unlock()
54
+
55
+		channelName := params[1]
56
+		channelKey, err := CasefoldChannel(channelName)
57
+		if err != nil {
58
+			client.ChanServNotice("Channel name is not valid")
59
+			return
60
+		}
61
+
62
+		channelInfo := server.channels.Get(channelKey)
63
+		if channelInfo == nil {
64
+			client.ChanServNotice("You must be an oper on the channel to register it")
65
+			return
66
+		}
67
+
68
+		if !channelInfo.ClientIsAtLeast(client, ChannelOperator) {
69
+			client.ChanServNotice("You must be an oper on the channel to register it")
70
+			return
71
+		}
72
+
73
+		server.store.Update(func(tx *buntdb.Tx) error {
74
+			currentChan := server.loadChannelNoMutex(tx, channelKey)
75
+			if currentChan != nil {
76
+				client.ChanServNotice("Channel is already registered")
77
+				return nil
78
+			}
79
+
80
+			account := client.account
81
+			if account == nil {
82
+				client.ChanServNotice("You must be logged in to register a channel")
83
+				return nil
84
+			}
85
+
86
+			chanRegInfo := RegisteredChannel{
87
+				Name:         channelName,
88
+				RegisteredAt: time.Now(),
89
+				Founder:      account.Name,
90
+				Topic:        channelInfo.topic,
91
+				TopicSetBy:   channelInfo.topicSetBy,
92
+				TopicSetTime: channelInfo.topicSetTime,
93
+			}
94
+			server.saveChannelNoMutex(tx, channelKey, chanRegInfo)
95
+
96
+			client.ChanServNotice(fmt.Sprintf("Channel %s successfully registered", channelName))
97
+
98
+			server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
99
+
100
+			channelInfo.membersMutex.Lock()
101
+			defer channelInfo.membersMutex.Unlock()
102
+
103
+			// give them founder privs
104
+			change := channelInfo.applyModeMemberNoMutex(client, ChannelFounder, Add, client.nickCasefolded)
105
+			if change != nil {
106
+				//TODO(dan): we should change the name of String and make it return a slice here
107
+				//TODO(dan): unify this code with code in modes.go
108
+				args := append([]string{channelName}, strings.Split(change.String(), " ")...)
109
+				for member := range channelInfo.members {
110
+					member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
111
+				}
112
+			}
113
+
114
+			return nil
115
+		})
116
+	} else {
117
+		client.ChanServNotice("Sorry, I don't know that command")
118
+	}
119
+}

+ 16
- 0
irc/commands.go Voir le fichier

@@ -74,6 +74,14 @@ var Commands = map[string]Command{
74 74
 		usablePreReg: true,
75 75
 		minParams:    1,
76 76
 	},
77
+	"CHANSERV": {
78
+		handler:   csHandler,
79
+		minParams: 1,
80
+	},
81
+	"CS": {
82
+		handler:   csHandler,
83
+		minParams: 1,
84
+	},
77 85
 	"DEBUG": {
78 86
 		handler:   debugHandler,
79 87
 		minParams: 1,
@@ -143,6 +151,10 @@ var Commands = map[string]Command{
143 151
 		usablePreReg: true,
144 152
 		minParams:    1,
145 153
 	},
154
+	"NICKSERV": {
155
+		handler:   nsHandler,
156
+		minParams: 1,
157
+	},
146 158
 	"NOTICE": {
147 159
 		handler:   noticeHandler,
148 160
 		minParams: 2,
@@ -155,6 +167,10 @@ var Commands = map[string]Command{
155 167
 		handler:   npcaHandler,
156 168
 		minParams: 3,
157 169
 	},
170
+	"NS": {
171
+		handler:   nsHandler,
172
+		minParams: 1,
173
+	},
158 174
 	"OPER": {
159 175
 		handler:   operHandler,
160 176
 		minParams: 2,

+ 20
- 0
irc/help.go Voir le fichier

@@ -84,6 +84,16 @@ longer away.`,
84 84
 Used in capability negotiation. See the IRCv3 specs for more info:
85 85
 http://ircv3.net/specs/core/capability-negotiation-3.1.html
86 86
 http://ircv3.net/specs/core/capability-negotiation-3.2.html`,
87
+	},
88
+	"chanserv": {
89
+		text: `CHANSERV <subcommand> [params]
90
+
91
+ChanServ controls channel registrations.`,
92
+	},
93
+	"cs": {
94
+		text: `CS <subcommand> [params]
95
+
96
+ChanServ controls channel registrations.`,
87 97
 	},
88 98
 	"debug": {
89 99
 		oper: true,
@@ -239,6 +249,11 @@ view the channel membership prefixes supported by this server, see the help for
239 249
 		text: `NICK <newnick>
240 250
 
241 251
 Sets your nickname to the new given one.`,
252
+	},
253
+	"nickserv": {
254
+		text: `NICKSERV <subcommand> [params]
255
+
256
+NickServ controls accounts and user registrations.`,
242 257
 	},
243 258
 	"notice": {
244 259
 		text: `NOTICE <target>{,<target>} <text to be sent>
@@ -258,6 +273,11 @@ Requires the roleplay mode (+E) to be set on the target.`,
258 273
 The NPC command is used to send an action to the target as the source.
259 274
 
260 275
 Requires the roleplay mode (+E) to be set on the target.`,
276
+	},
277
+	"ns": {
278
+		text: `NS <subcommand> [params]
279
+
280
+NickServ controls accounts and user registrations.`,
261 281
 	},
262 282
 	"oper": {
263 283
 		text: `OPER <name> <password>

+ 10
- 2
irc/nickname.go Voir le fichier

@@ -11,6 +11,14 @@ import (
11 11
 	"github.com/DanielOaks/girc-go/ircmsg"
12 12
 )
13 13
 
14
+var (
15
+	restrictedNicknames = map[string]bool{
16
+		"=scene=":  true, // used for rp commands
17
+		"chanserv": true,
18
+		"nickserv": true,
19
+	}
20
+)
21
+
14 22
 // NICK <nickname>
15 23
 func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
16 24
 	if !client.authorized {
@@ -26,7 +34,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
26 34
 		return false
27 35
 	}
28 36
 
29
-	if err != nil || len(nicknameRaw) > server.limits.NickLen || nickname == "=scene=" {
37
+	if err != nil || len(nicknameRaw) > server.limits.NickLen || restrictedNicknames[nickname] {
30 38
 		client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, nicknameRaw, "Erroneous nickname")
31 39
 		return false
32 40
 	}
@@ -70,7 +78,7 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
70 78
 		return false
71 79
 	}
72 80
 
73
-	if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen || nickname == "=scene=" {
81
+	if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen || restrictedNicknames[nickname] {
74 82
 		client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
75 83
 		return false
76 84
 	}

+ 24
- 0
irc/nickserv.go Voir le fichier

@@ -0,0 +1,24 @@
1
+// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"strings"
8
+
9
+	"github.com/DanielOaks/girc-go/ircmsg"
10
+)
11
+
12
+// nsHandler handles the /NS and /NICKSERV commands
13
+func nsHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
14
+	server.nickservReceivePrivmsg(client, strings.Join(msg.Params, " "))
15
+	return false
16
+}
17
+
18
+func (server *Server) nickservReceiveNotice(client *Client, message string) {
19
+	// do nothing
20
+}
21
+
22
+func (server *Server) nickservReceivePrivmsg(client *Client, message string) {
23
+	client.Notice("NickServ is not yet implemented, sorry!")
24
+}

+ 33
- 16
irc/server.go Voir le fichier

@@ -114,6 +114,8 @@ type Server struct {
114 114
 	operclasses                  map[string]OperClass
115 115
 	password                     []byte
116 116
 	passwords                    *PasswordManager
117
+	registeredChannels           map[string]*RegisteredChannel
118
+	registeredChannelsMutex      sync.RWMutex
117 119
 	rehashMutex                  sync.Mutex
118 120
 	rehashSignal                 chan os.Signal
119 121
 	restAPI                      *RestAPIConfig
@@ -185,9 +187,10 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (*
185 187
 	}
186 188
 
187 189
 	server := &Server{
188
-		accounts:                     make(map[string]*ClientAccount),
189 190
 		accountAuthenticationEnabled: config.Accounts.AuthenticationEnabled,
191
+		accounts:                     make(map[string]*ClientAccount),
190 192
 		channels:                     make(ChannelNameMap),
193
+		checkIdent:                   config.Server.CheckIdent,
191 194
 		clients:                      NewClientLookupSet(),
192 195
 		commands:                     make(chan Command),
193 196
 		configFilename:               configFilename,
@@ -209,21 +212,21 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (*
209 212
 				Rest: config.Limits.LineLen.Rest,
210 213
 			},
211 214
 		},
212
-		listeners:      make(map[string]ListenerInterface),
213
-		logger:         logger,
214
-		monitoring:     make(map[string][]Client),
215
-		name:           config.Server.Name,
216
-		nameCasefolded: casefoldedName,
217
-		networkName:    config.Network.Name,
218
-		newConns:       make(chan clientConn),
219
-		operclasses:    *operClasses,
220
-		operators:      opers,
221
-		signals:        make(chan os.Signal, len(ServerExitSignals)),
222
-		stsEnabled:     config.Server.STS.Enabled,
223
-		rehashSignal:   make(chan os.Signal, 1),
224
-		restAPI:        &config.Server.RestAPI,
225
-		whoWas:         NewWhoWasList(config.Limits.WhowasEntries),
226
-		checkIdent:     config.Server.CheckIdent,
215
+		listeners:          make(map[string]ListenerInterface),
216
+		logger:             logger,
217
+		monitoring:         make(map[string][]Client),
218
+		name:               config.Server.Name,
219
+		nameCasefolded:     casefoldedName,
220
+		networkName:        config.Network.Name,
221
+		newConns:           make(chan clientConn),
222
+		operators:          opers,
223
+		operclasses:        *operClasses,
224
+		registeredChannels: make(map[string]*RegisteredChannel),
225
+		rehashSignal:       make(chan os.Signal, 1),
226
+		restAPI:            &config.Server.RestAPI,
227
+		signals:            make(chan os.Signal, len(ServerExitSignals)),
228
+		stsEnabled:         config.Server.STS.Enabled,
229
+		whoWas:             NewWhoWasList(config.Limits.WhowasEntries),
227 230
 	}
228 231
 
229 232
 	// open data store
@@ -949,6 +952,13 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
949 952
 			channel.SplitPrivMsg(msgid, lowestPrefix, clientOnlyTags, client, splitMsg)
950 953
 		} else {
951 954
 			target, err = CasefoldName(targetString)
955
+			if target == "chanserv" {
956
+				server.chanservReceivePrivmsg(client, message)
957
+				continue
958
+			} else if target == "nickserv" {
959
+				server.nickservReceivePrivmsg(client, message)
960
+				continue
961
+			}
952 962
 			user := server.clients.Get(target)
953 963
 			if err != nil || user == nil {
954 964
 				if len(target) > 0 {
@@ -1593,6 +1603,13 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1593 1603
 			if err != nil {
1594 1604
 				continue
1595 1605
 			}
1606
+			if target == "chanserv" {
1607
+				server.chanservReceiveNotice(client, message)
1608
+				continue
1609
+			} else if target == "nickserv" {
1610
+				server.nickservReceiveNotice(client, message)
1611
+				continue
1612
+			}
1596 1613
 
1597 1614
 			user := server.clients.Get(target)
1598 1615
 			if user == nil {

Chargement…
Annuler
Enregistrer