Browse Source

Merge pull request #325 from slingamn/services.1

services refactor
tags/v1.0.0-rc1
Shivaram Lingamneni 5 years ago
parent
commit
3db6c9472b
No account linked to committer's email address
9 changed files with 254 additions and 168 deletions
  1. 4
    0
      irc/accounts.go
  2. 28
    38
      irc/chanserv.go
  3. 1
    1
      irc/client_lookup_set.go
  4. 47
    44
      irc/hostserv.go
  5. 2
    1
      irc/nickname.go
  6. 71
    56
      irc/nickserv.go
  7. 49
    15
      irc/services.go
  8. 0
    13
      irc/utils/args.go
  9. 52
    0
      irc/utils/fieldsn.go

+ 4
- 0
irc/accounts.go View File

@@ -187,6 +187,10 @@ func (am *AccountManager) EnforcementStatus(nick string) (account string, method
187 187
 	defer am.RUnlock()
188 188
 
189 189
 	account = am.nickToAccount[cfnick]
190
+	if account == "" {
191
+		method = NickReservationNone
192
+		return
193
+	}
190 194
 	method = am.accountToMethod[account]
191 195
 	// if they don't have a custom setting, or customization is disabled, use the default
192 196
 	if method == NickReservationOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {

+ 28
- 38
irc/chanserv.go View File

@@ -15,7 +15,6 @@ import (
15 15
 	"github.com/goshuirc/irc-go/ircfmt"
16 16
 	"github.com/oragono/oragono/irc/modes"
17 17
 	"github.com/oragono/oragono/irc/sno"
18
-	"github.com/oragono/oragono/irc/utils"
19 18
 )
20 19
 
21 20
 const chanservHelp = `ChanServ lets you register and manage channels.
@@ -26,8 +25,8 @@ To see in-depth help for a specific ChanServ command, try:
26 25
 Here are the commands you can use:
27 26
 %s`
28 27
 
29
-func chanregEnabled(server *Server) bool {
30
-	return server.ChannelRegistrationEnabled()
28
+func chanregEnabled(config *Config) bool {
29
+	return config.Channels.Registration.Enabled
31 30
 }
32 31
 
33 32
 var (
@@ -41,6 +40,7 @@ this command if you're the founder of the channel.`,
41 40
 			helpShort:    `$bOP$b makes the given user (or yourself) a channel admin.`,
42 41
 			authRequired: true,
43 42
 			enabled:      chanregEnabled,
43
+			minParams:    1,
44 44
 		},
45 45
 		"register": {
46 46
 			handler: csRegisterHandler,
@@ -52,6 +52,7 @@ remembered.`,
52 52
 			helpShort:    `$bREGISTER$b lets you own a given channel.`,
53 53
 			authRequired: true,
54 54
 			enabled:      chanregEnabled,
55
+			minParams:    1,
55 56
 		},
56 57
 		"unregister": {
57 58
 			handler: csUnregisterHandler,
@@ -62,6 +63,7 @@ To prevent accidental unregistrations, a verification code is required;
62 63
 invoking the command without a code will display the necessary code.`,
63 64
 			helpShort: `$bUNREGISTER$b deletes a channel registration.`,
64 65
 			enabled:   chanregEnabled,
66
+			minParams: 1,
65 67
 		},
66 68
 		"drop": {
67 69
 			aliasOf: "unregister",
@@ -77,6 +79,7 @@ accounts and modes, use $bAMODE #channel$b. Note that users are always
77 79
 referenced by their registered account names, not their nicknames.`,
78 80
 			helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`,
79 81
 			enabled:   chanregEnabled,
82
+			minParams: 1,
80 83
 		},
81 84
 	}
82 85
 )
@@ -86,8 +89,8 @@ func csNotice(rb *ResponseBuffer, text string) {
86 89
 	rb.Add(nil, "ChanServ", "NOTICE", rb.target.Nick(), text)
87 90
 }
88 91
 
89
-func csAmodeHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
90
-	channelName, modeChange := utils.ExtractParam(params)
92
+func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
93
+	channelName := params[0]
91 94
 
92 95
 	channel := server.channels.Get(channelName)
93 96
 	if channel == nil {
@@ -98,7 +101,7 @@ func csAmodeHandler(server *Server, client *Client, command, params string, rb *
98 101
 		return
99 102
 	}
100 103
 
101
-	modeChanges, unknown := modes.ParseChannelModeChanges(strings.Fields(modeChange)...)
104
+	modeChanges, unknown := modes.ParseChannelModeChanges(params[1:]...)
102 105
 	var change modes.ModeChange
103 106
 	if len(modeChanges) > 1 || len(unknown) > 0 {
104 107
 		csNotice(rb, client.t("Invalid mode change"))
@@ -159,27 +162,13 @@ func csAmodeHandler(server *Server, client *Client, command, params string, rb *
159 162
 	}
160 163
 }
161 164
 
162
-func csOpHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
163
-	channelName, clientToOp := utils.ExtractParam(params)
164
-
165
-	if channelName == "" {
166
-		csNotice(rb, ircfmt.Unescape(client.t("Syntax: $bOP #channel [nickname]$b")))
167
-		return
168
-	}
169
-
170
-	clientToOp = strings.TrimSpace(clientToOp)
171
-
172
-	channelKey, err := CasefoldChannel(channelName)
173
-	if err != nil {
174
-		csNotice(rb, client.t("Channel name is not valid"))
175
-		return
176
-	}
177
-
178
-	channelInfo := server.channels.Get(channelKey)
165
+func csOpHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
166
+	channelInfo := server.channels.Get(params[0])
179 167
 	if channelInfo == nil {
180 168
 		csNotice(rb, client.t("Channel does not exist"))
181 169
 		return
182 170
 	}
171
+	channelName := channelInfo.Name()
183 172
 
184 173
 	clientAccount := client.Account()
185 174
 	if clientAccount == "" || clientAccount != channelInfo.Founder() {
@@ -188,10 +177,9 @@ func csOpHandler(server *Server, client *Client, command, params string, rb *Res
188 177
 	}
189 178
 
190 179
 	var target *Client
191
-	if clientToOp != "" {
192
-		casefoldedNickname, err := CasefoldName(clientToOp)
193
-		target = server.clients.Get(casefoldedNickname)
194
-		if err != nil || target == nil {
180
+	if len(params) > 1 {
181
+		target = server.clients.Get(params[1])
182
+		if target == nil {
195 183
 			csNotice(rb, client.t("Could not find given client"))
196 184
 			return
197 185
 		}
@@ -216,16 +204,13 @@ func csOpHandler(server *Server, client *Client, command, params string, rb *Res
216 204
 
217 205
 	csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
218 206
 
219
-	server.logger.Info("chanserv", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.nick, clientToOp, channelName))
220
-	server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.nickMaskString, clientToOp, channelName))
207
+	tnick := target.Nick()
208
+	server.logger.Info("services", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.Nick(), tnick, channelName))
209
+	server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.NickMaskString(), tnick, channelName))
221 210
 }
222 211
 
223
-func csRegisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
224
-	channelName := strings.TrimSpace(params)
225
-	if channelName == "" {
226
-		csNotice(rb, ircfmt.Unescape(client.t("Syntax: $bREGISTER #channel$b")))
227
-		return
228
-	}
212
+func csRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
213
+	channelName := params[0]
229 214
 
230 215
 	channelKey, err := CasefoldChannel(channelName)
231 216
 	if err != nil {
@@ -251,7 +236,7 @@ func csRegisterHandler(server *Server, client *Client, command, params string, r
251 236
 
252 237
 	csNotice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
253 238
 
254
-	server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
239
+	server.logger.Info("services", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
255 240
 	server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
256 241
 
257 242
 	// give them founder privs
@@ -266,8 +251,13 @@ func csRegisterHandler(server *Server, client *Client, command, params string, r
266 251
 	}
267 252
 }
268 253
 
269
-func csUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
270
-	channelName, verificationCode := utils.ExtractParam(params)
254
+func csUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
255
+	channelName := params[0]
256
+	var verificationCode string
257
+	if len(params) > 1 {
258
+		verificationCode = params[1]
259
+	}
260
+
271 261
 	channelKey, err := CasefoldChannel(channelName)
272 262
 	if channelKey == "" || err != nil {
273 263
 		csNotice(rb, client.t("Channel name is not valid"))

+ 1
- 1
irc/client_lookup_set.go View File

@@ -129,7 +129,7 @@ func (clients *ClientManager) SetNick(client *Client, newNick string) error {
129 129
 	if currentNewEntry != nil && currentNewEntry != client {
130 130
 		return errNicknameInUse
131 131
 	}
132
-	if method == NickReservationStrict && reservedAccount != client.Account() {
132
+	if method == NickReservationStrict && reservedAccount != "" && reservedAccount != client.Account() {
133 133
 		return errNicknameReserved
134 134
 	}
135 135
 	clients.removeInternal(client)

+ 47
- 44
irc/hostserv.go View File

@@ -7,10 +7,7 @@ import (
7 7
 	"errors"
8 8
 	"fmt"
9 9
 	"regexp"
10
-	"strings"
11 10
 	"time"
12
-
13
-	"github.com/oragono/oragono/irc/utils"
14 11
 )
15 12
 
16 13
 const hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
@@ -29,13 +26,12 @@ var (
29 26
 	defaultValidVhostRegex = regexp.MustCompile(`^[0-9A-Za-z.\-_/]+$`)
30 27
 )
31 28
 
32
-func hostservEnabled(server *Server) bool {
33
-	return server.AccountConfig().VHosts.Enabled
29
+func hostservEnabled(config *Config) bool {
30
+	return config.Accounts.VHosts.Enabled
34 31
 }
35 32
 
36
-func hostservRequestsEnabled(server *Server) bool {
37
-	ac := server.AccountConfig()
38
-	return ac.VHosts.Enabled && ac.VHosts.UserRequests.Enabled
33
+func hostservRequestsEnabled(config *Config) bool {
34
+	return config.Accounts.VHosts.Enabled && config.Accounts.VHosts.UserRequests.Enabled
39 35
 }
40 36
 
41 37
 var (
@@ -67,16 +63,16 @@ then be approved by a server operator.`,
67 63
 			helpShort:    `$bREQUEST$b requests a new vhost, pending operator approval.`,
68 64
 			authRequired: true,
69 65
 			enabled:      hostservRequestsEnabled,
66
+			minParams:    1,
70 67
 		},
71 68
 		"status": {
72 69
 			handler: hsStatusHandler,
73
-			help: `Syntax: $bSTATUS$b
70
+			help: `Syntax: $bSTATUS [user]$b
74 71
 
75 72
 STATUS displays your current vhost, if any, and the status of your most recent
76
-request for a new one.`,
77
-			helpShort:    `$bSTATUS$b shows your vhost and request status.`,
78
-			authRequired: true,
79
-			enabled:      hostservEnabled,
73
+request for a new one. A server operator can view someone else's status.`,
74
+			helpShort: `$bSTATUS$b shows your vhost and request status.`,
75
+			enabled:   hostservEnabled,
80 76
 		},
81 77
 		"set": {
82 78
 			handler: hsSetHandler,
@@ -86,6 +82,7 @@ SET sets a user's vhost, bypassing the request system.`,
86 82
 			helpShort: `$bSET$b sets a user's vhost.`,
87 83
 			capabs:    []string{"vhosts"},
88 84
 			enabled:   hostservEnabled,
85
+			minParams: 2,
89 86
 		},
90 87
 		"del": {
91 88
 			handler: hsSetHandler,
@@ -95,6 +92,7 @@ DEL deletes a user's vhost.`,
95 92
 			helpShort: `$bDEL$b deletes a user's vhost.`,
96 93
 			capabs:    []string{"vhosts"},
97 94
 			enabled:   hostservEnabled,
95
+			minParams: 1,
98 96
 		},
99 97
 		"waiting": {
100 98
 			handler: hsWaitingHandler,
@@ -114,6 +112,7 @@ APPROVE approves a user's vhost request.`,
114 112
 			helpShort: `$bAPPROVE$b approves a user's vhost request.`,
115 113
 			capabs:    []string{"vhosts"},
116 114
 			enabled:   hostservEnabled,
115
+			minParams: 1,
117 116
 		},
118 117
 		"reject": {
119 118
 			handler: hsRejectHandler,
@@ -124,6 +123,8 @@ for the rejection.`,
124 123
 			helpShort: `$bREJECT$b rejects a user's vhost request.`,
125 124
 			capabs:    []string{"vhosts"},
126 125
 			enabled:   hostservEnabled,
126
+			minParams: 1,
127
+			maxParams: 2,
127 128
 		},
128 129
 	}
129 130
 )
@@ -146,7 +147,7 @@ func hsNotifyChannel(server *Server, message string) {
146 147
 	}
147 148
 }
148 149
 
149
-func hsOnOffHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
150
+func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
150 151
 	enable := false
151 152
 	if command == "on" {
152 153
 		enable = true
@@ -162,8 +163,8 @@ func hsOnOffHandler(server *Server, client *Client, command, params string, rb *
162 163
 	}
163 164
 }
164 165
 
165
-func hsRequestHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
166
-	vhost, _ := utils.ExtractParam(params)
166
+func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
167
+	vhost := params[0]
167 168
 	if validateVhost(server, vhost, false) != nil {
168 169
 		hsNotice(rb, client.t("Invalid vhost"))
169 170
 		return
@@ -195,12 +196,24 @@ func hsRequestHandler(server *Server, client *Client, command, params string, rb
195 196
 	}
196 197
 }
197 198
 
198
-func hsStatusHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
199
-	accountName := client.Account()
199
+func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
200
+	var accountName string
201
+	if len(params) > 0 {
202
+		if !client.HasRoleCapabs("vhosts") {
203
+			hsNotice(rb, client.t("Command restricted"))
204
+			return
205
+		}
206
+		accountName = params[0]
207
+	} else {
208
+		accountName = client.Account()
209
+	}
210
+
200 211
 	account, err := server.accounts.LoadAccount(accountName)
201 212
 	if err != nil {
202
-		server.logger.Warning("internal", "error loading account info", accountName, err.Error())
203
-		hsNotice(rb, client.t("An error occurred"))
213
+		if err != errAccountDoesNotExist {
214
+			server.logger.Warning("internal", "error loading account info", accountName, err.Error())
215
+		}
216
+		hsNotice(rb, client.t("No such account"))
204 217
 		return
205 218
 	}
206 219
 
@@ -232,23 +245,18 @@ func validateVhost(server *Server, vhost string, oper bool) error {
232 245
 	return nil
233 246
 }
234 247
 
235
-func hsSetHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
236
-	var user, vhost string
237
-	user, params = utils.ExtractParam(params)
238
-	if user == "" {
239
-		hsNotice(rb, client.t("A user is required"))
240
-		return
241
-	}
248
+func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
249
+	user := params[0]
250
+	var vhost string
251
+
242 252
 	if command == "set" {
243
-		vhost, _ = utils.ExtractParam(params)
253
+		vhost = params[1]
244 254
 		if validateVhost(server, vhost, true) != nil {
245 255
 			hsNotice(rb, client.t("Invalid vhost"))
246 256
 			return
247 257
 		}
248
-	} else if command != "del" {
249
-		server.logger.Warning("internal", "invalid hostserv set command", command)
250
-		return
251 258
 	}
259
+	// else: command == "del", vhost == ""
252 260
 
253 261
 	_, err := server.accounts.VHostSet(user, vhost)
254 262
 	if err != nil {
@@ -260,7 +268,7 @@ func hsSetHandler(server *Server, client *Client, command, params string, rb *Re
260 268
 	}
261 269
 }
262 270
 
263
-func hsWaitingHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
271
+func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
264 272
 	requests, total := server.accounts.VHostListRequests(10)
265 273
 	hsNotice(rb, fmt.Sprintf(client.t("There are %d pending requests for vhosts (%d displayed)"), total, len(requests)))
266 274
 	for i, request := range requests {
@@ -268,12 +276,8 @@ func hsWaitingHandler(server *Server, client *Client, command, params string, rb
268 276
 	}
269 277
 }
270 278
 
271
-func hsApproveHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
272
-	user, _ := utils.ExtractParam(params)
273
-	if user == "" {
274
-		hsNotice(rb, client.t("A user is required"))
275
-		return
276
-	}
279
+func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
280
+	user := params[0]
277 281
 
278 282
 	vhostInfo, err := server.accounts.VHostApprove(user)
279 283
 	if err != nil {
@@ -288,13 +292,12 @@ func hsApproveHandler(server *Server, client *Client, command, params string, rb
288 292
 	}
289 293
 }
290 294
 
291
-func hsRejectHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
292
-	user, params := utils.ExtractParam(params)
293
-	if user == "" {
294
-		hsNotice(rb, client.t("A user is required"))
295
-		return
295
+func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
296
+	var reason string
297
+	user := params[0]
298
+	if len(params) > 1 {
299
+		reason = params[1]
296 300
 	}
297
-	reason := strings.TrimSpace(params)
298 301
 
299 302
 	vhostInfo, err := server.accounts.VHostReject(user, reason)
300 303
 	if err != nil {

+ 2
- 1
irc/nickname.go View File

@@ -15,9 +15,10 @@ import (
15 15
 )
16 16
 
17 17
 var (
18
+	// anything added here MUST be casefolded:
18 19
 	restrictedNicknames = map[string]bool{
19 20
 		"=scene=":  true, // used for rp commands
20
-		"HistServ": true, // TODO(slingamn) this should become a real service
21
+		"histserv": true, // TODO(slingamn) this should become a real service
21 22
 	}
22 23
 )
23 24
 

+ 71
- 56
irc/nickserv.go View File

@@ -8,25 +8,22 @@ import (
8 8
 	"strings"
9 9
 
10 10
 	"github.com/goshuirc/irc-go/ircfmt"
11
-	"github.com/oragono/oragono/irc/utils"
12 11
 )
13 12
 
14 13
 // "enabled" callbacks for specific nickserv commands
15
-func servCmdRequiresAccreg(server *Server) bool {
16
-	return server.AccountConfig().Registration.Enabled
14
+func servCmdRequiresAccreg(config *Config) bool {
15
+	return config.Accounts.Registration.Enabled
17 16
 }
18 17
 
19
-func servCmdRequiresAuthEnabled(server *Server) bool {
20
-	return server.AccountConfig().AuthenticationEnabled
18
+func servCmdRequiresAuthEnabled(config *Config) bool {
19
+	return config.Accounts.AuthenticationEnabled
21 20
 }
22 21
 
23
-func nsGroupEnabled(server *Server) bool {
24
-	conf := server.Config()
25
-	return conf.Accounts.AuthenticationEnabled && conf.Accounts.NickReservation.Enabled
22
+func nsGroupEnabled(config *Config) bool {
23
+	return config.Accounts.AuthenticationEnabled && config.Accounts.NickReservation.Enabled
26 24
 }
27 25
 
28
-func nsEnforceEnabled(server *Server) bool {
29
-	config := server.Config()
26
+func nsEnforceEnabled(config *Config) bool {
30 27
 	return config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement
31 28
 }
32 29
 
@@ -73,6 +70,7 @@ GHOST disconnects the given user from the network if they're logged in with the
73 70
 same user account, letting you reclaim your nickname.`,
74 71
 			helpShort:    `$bGHOST$b reclaims your nickname.`,
75 72
 			authRequired: true,
73
+			minParams:    1,
76 74
 		},
77 75
 		"group": {
78 76
 			handler: nsGroupHandler,
@@ -84,7 +82,6 @@ users from changing to it (or forcing them to rename).`,
84 82
 			enabled:      nsGroupEnabled,
85 83
 			authRequired: true,
86 84
 		},
87
-
88 85
 		"identify": {
89 86
 			handler: nsIdentifyHandler,
90 87
 			help: `Syntax: $bIDENTIFY <username> [password]$b
@@ -92,6 +89,7 @@ users from changing to it (or forcing them to rename).`,
92 89
 IDENTIFY lets you login to the given username using either password auth, or
93 90
 certfp (your client certificate) if a password is not given.`,
94 91
 			helpShort: `$bIDENTIFY$b lets you login to your account.`,
92
+			minParams: 1,
95 93
 		},
96 94
 		"info": {
97 95
 			handler: nsInfoHandler,
@@ -113,6 +111,7 @@ If the password is left out, your account will be registered to your TLS client
113 111
 certificate (and you will need to use that certificate to login in future).`,
114 112
 			helpShort: `$bREGISTER$b lets you register a user account.`,
115 113
 			enabled:   servCmdRequiresAccreg,
114
+			minParams: 2,
116 115
 		},
117 116
 		"sadrop": {
118 117
 			handler: nsDropHandler,
@@ -122,6 +121,7 @@ SADROP forcibly de-links the given nickname from the attached user account.`,
122 121
 			helpShort: `$bSADROP$b forcibly de-links the given nickname from its user account.`,
123 122
 			capabs:    []string{"accreg"},
124 123
 			enabled:   servCmdRequiresAccreg,
124
+			minParams: 1,
125 125
 		},
126 126
 		"unregister": {
127 127
 			handler: nsUnregisterHandler,
@@ -132,6 +132,8 @@ IRC operator with the correct permissions). To prevent accidental
132 132
 unregistrations, a verification code is required; invoking the command without
133 133
 a code will display the necessary code.`,
134 134
 			helpShort: `$bUNREGISTER$b lets you delete your user account.`,
135
+			enabled:   servCmdRequiresAccreg,
136
+			minParams: 1,
135 137
 		},
136 138
 		"verify": {
137 139
 			handler: nsVerifyHandler,
@@ -141,6 +143,7 @@ VERIFY lets you complete an account registration, if the server requires email
141 143
 or other verification.`,
142 144
 			helpShort: `$bVERIFY$b lets you complete account registration.`,
143 145
 			enabled:   servCmdRequiresAccreg,
146
+			minParams: 2,
144 147
 		},
145 148
 		"passwd": {
146 149
 			handler: nsPasswdHandler,
@@ -153,6 +156,7 @@ with the correct permissions, you can use PASSWD to reset someone else's
153 156
 password by supplying their username and then the desired password.`,
154 157
 			helpShort: `$bPASSWD$b lets you change your password.`,
155 158
 			enabled:   servCmdRequiresAuthEnabled,
159
+			minParams: 2,
156 160
 		},
157 161
 	}
158 162
 )
@@ -162,9 +166,14 @@ func nsNotice(rb *ResponseBuffer, text string) {
162 166
 	rb.Add(nil, "NickServ", "NOTICE", rb.target.Nick(), text)
163 167
 }
164 168
 
165
-func nsDropHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
169
+func nsDropHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
166 170
 	sadrop := command == "sadrop"
167
-	nick, _ := utils.ExtractParam(params)
171
+	var nick string
172
+	if len(params) > 0 {
173
+		nick = params[0]
174
+	} else {
175
+		nick = client.NickCasefolded()
176
+	}
168 177
 
169 178
 	err := server.accounts.SetNickReserved(client, nick, sadrop, false)
170 179
 	if err == nil {
@@ -173,15 +182,13 @@ func nsDropHandler(server *Server, client *Client, command, params string, rb *R
173 182
 		nsNotice(rb, client.t("You're not logged into an account"))
174 183
 	} else if err == errAccountCantDropPrimaryNick {
175 184
 		nsNotice(rb, client.t("You can't ungroup your primary nickname (try unregistering your account instead)"))
176
-	} else if err == errNicknameReserved {
177
-		nsNotice(rb, client.t("That nickname is already reserved by someone else"))
178 185
 	} else {
179 186
 		nsNotice(rb, client.t("Could not ungroup nick"))
180 187
 	}
181 188
 }
182 189
 
183
-func nsGhostHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
184
-	nick, _ := utils.ExtractParam(params)
190
+func nsGhostHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
191
+	nick := params[0]
185 192
 
186 193
 	ghost := server.clients.Get(nick)
187 194
 	if ghost == nil {
@@ -207,7 +214,7 @@ func nsGhostHandler(server *Server, client *Client, command, params string, rb *
207 214
 	ghost.destroy(false)
208 215
 }
209 216
 
210
-func nsGroupHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
217
+func nsGroupHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
211 218
 	nick := client.NickCasefolded()
212 219
 	err := server.accounts.SetNickReserved(client, nick, false, true)
213 220
 	if err == nil {
@@ -230,13 +237,17 @@ func nsLoginThrottleCheck(client *Client, rb *ResponseBuffer) (success bool) {
230 237
 	return true
231 238
 }
232 239
 
233
-func nsIdentifyHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
240
+func nsIdentifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
234 241
 	loginSuccessful := false
235 242
 
236
-	username, passphrase := utils.ExtractParam(params)
243
+	username := params[0]
244
+	var passphrase string
245
+	if len(params) > 1 {
246
+		passphrase = params[1]
247
+	}
237 248
 
238 249
 	// try passphrase
239
-	if username != "" && passphrase != "" {
250
+	if passphrase != "" {
240 251
 		if !nsLoginThrottleCheck(client, rb) {
241 252
 			return
242 253
 		}
@@ -257,18 +268,23 @@ func nsIdentifyHandler(server *Server, client *Client, command, params string, r
257 268
 	}
258 269
 }
259 270
 
260
-func nsInfoHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
261
-	nick, _ := utils.ExtractParam(params)
262
-
263
-	if nick == "" {
264
-		nick = client.Nick()
265
-	}
266
-
267
-	accountName := nick
268
-	if server.AccountConfig().NickReservation.Enabled {
269
-		accountName = server.accounts.NickToAccount(nick)
271
+func nsInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
272
+	var accountName string
273
+	if len(params) > 0 {
274
+		nick := params[0]
275
+		if server.AccountConfig().NickReservation.Enabled {
276
+			accountName = server.accounts.NickToAccount(nick)
277
+			if accountName == "" {
278
+				nsNotice(rb, client.t("That nickname is not registered"))
279
+				return
280
+			}
281
+		} else {
282
+			accountName = nick
283
+		}
284
+	} else {
285
+		accountName = client.Account()
270 286
 		if accountName == "" {
271
-			nsNotice(rb, client.t("That nickname is not registered"))
287
+			nsNotice(rb, client.t("You're not logged into an account"))
272 288
 			return
273 289
 		}
274 290
 	}
@@ -276,6 +292,7 @@ func nsInfoHandler(server *Server, client *Client, command, params string, rb *R
276 292
 	account, err := server.accounts.LoadAccount(accountName)
277 293
 	if err != nil || !account.Verified {
278 294
 		nsNotice(rb, client.t("Account does not exist"))
295
+		return
279 296
 	}
280 297
 
281 298
 	nsNotice(rb, fmt.Sprintf(client.t("Account: %s"), account.Name))
@@ -287,10 +304,13 @@ func nsInfoHandler(server *Server, client *Client, command, params string, rb *R
287 304
 	}
288 305
 }
289 306
 
290
-func nsRegisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
307
+func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
291 308
 	// get params
292
-	username, afterUsername := utils.ExtractParam(params)
293
-	email, passphrase := utils.ExtractParam(afterUsername)
309
+	username, email := params[0], params[1]
310
+	var passphrase string
311
+	if len(params) > 0 {
312
+		passphrase = params[2]
313
+	}
294 314
 
295 315
 	if !server.AccountConfig().Registration.Enabled {
296 316
 		nsNotice(rb, client.t("Account registration has been disabled"))
@@ -366,12 +386,11 @@ func nsRegisterHandler(server *Server, client *Client, command, params string, r
366 386
 	}
367 387
 }
368 388
 
369
-func nsUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
370
-	username, verificationCode := utils.ExtractParam(params)
371
-
372
-	if !server.AccountConfig().Registration.Enabled {
373
-		nsNotice(rb, client.t("Account registration has been disabled"))
374
-		return
389
+func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
390
+	username := params[0]
391
+	var verificationCode string
392
+	if len(params) > 1 {
393
+		verificationCode = params[1]
375 394
 	}
376 395
 
377 396
 	if username == "" {
@@ -415,9 +434,8 @@ func nsUnregisterHandler(server *Server, client *Client, command, params string,
415 434
 	}
416 435
 }
417 436
 
418
-func nsVerifyHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
419
-	username, code := utils.ExtractParam(params)
420
-
437
+func nsVerifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
438
+	username, code := params[0], params[1]
421 439
 	err := server.accounts.Verify(client, username, code)
422 440
 
423 441
 	var errorMessage string
@@ -435,7 +453,7 @@ func nsVerifyHandler(server *Server, client *Client, command, params string, rb
435 453
 	sendSuccessfulRegResponse(client, rb, true)
436 454
 }
437 455
 
438
-func nsPasswdHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
456
+func nsPasswdHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
439 457
 	var target string
440 458
 	var newPassword string
441 459
 	var errorMessage string
@@ -445,27 +463,26 @@ func nsPasswdHandler(server *Server, client *Client, command, params string, rb
445 463
 		return
446 464
 	}
447 465
 
448
-	fields := strings.Fields(params)
449
-	switch len(fields) {
466
+	switch len(params) {
450 467
 	case 2:
451 468
 		if !hasPrivs {
452 469
 			errorMessage = "Insufficient privileges"
453 470
 		} else {
454
-			target, newPassword = fields[0], fields[1]
471
+			target, newPassword = params[0], params[1]
455 472
 		}
456 473
 	case 3:
457 474
 		target = client.Account()
458 475
 		if target == "" {
459 476
 			errorMessage = "You're not logged into an account"
460
-		} else if fields[1] != fields[2] {
477
+		} else if params[1] != params[2] {
461 478
 			errorMessage = "Passwords do not match"
462 479
 		} else {
463 480
 			// check that they correctly supplied the preexisting password
464
-			_, err := server.accounts.checkPassphrase(target, fields[0])
481
+			_, err := server.accounts.checkPassphrase(target, params[0])
465 482
 			if err != nil {
466 483
 				errorMessage = "Password incorrect"
467 484
 			} else {
468
-				newPassword = fields[1]
485
+				newPassword = params[1]
469 486
 			}
470 487
 		}
471 488
 	default:
@@ -486,14 +503,12 @@ func nsPasswdHandler(server *Server, client *Client, command, params string, rb
486 503
 	}
487 504
 }
488 505
 
489
-func nsEnforceHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
490
-	arg := strings.TrimSpace(params)
491
-
492
-	if arg == "" {
506
+func nsEnforceHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
507
+	if len(params) == 0 {
493 508
 		status := server.accounts.getStoredEnforcementStatus(client.Account())
494 509
 		nsNotice(rb, fmt.Sprintf(client.t("Your current nickname enforcement is: %s"), status))
495 510
 	} else {
496
-		method, err := nickReservationFromString(arg)
511
+		method, err := nickReservationFromString(params[0])
497 512
 		if err != nil {
498 513
 			nsNotice(rb, client.t("Invalid parameters"))
499 514
 			return

+ 49
- 15
irc/services.go View File

@@ -11,7 +11,6 @@ import (
11 11
 
12 12
 	"github.com/goshuirc/irc-go/ircfmt"
13 13
 	"github.com/goshuirc/irc-go/ircmsg"
14
-
15 14
 	"github.com/oragono/oragono/irc/utils"
16 15
 )
17 16
 
@@ -28,11 +27,13 @@ type ircService struct {
28 27
 type serviceCommand struct {
29 28
 	aliasOf      string   // marks this command as an alias of another
30 29
 	capabs       []string // oper capabs the given user has to have to access this command
31
-	handler      func(server *Server, client *Client, command, params string, rb *ResponseBuffer)
30
+	handler      func(server *Server, client *Client, command string, params []string, rb *ResponseBuffer)
32 31
 	help         string
33 32
 	helpShort    string
34 33
 	authRequired bool
35
-	enabled      func(*Server) bool // is this command enabled in the server config?
34
+	enabled      func(*Config) bool // is this command enabled in the server config?
35
+	minParams    int
36
+	maxParams    int // split into at most n params, with last param containing remaining unsplit text
36 37
 }
37 38
 
38 39
 // looks up a command in the table of command definitions for a service, resolving aliases
@@ -90,7 +91,7 @@ HELP returns information on the given command.`,
90 91
 	helpShort: `$bHELP$b shows in-depth information about commands.`,
91 92
 }
92 93
 
93
-// this handles IRC commands like `/NICKSERV INFO`, translating into `/MSG NICKSERV INFO`
94
+// generic handler for IRC commands like `/NICKSERV INFO`
94 95
 func serviceCmdHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
95 96
 	service, ok := oragonoServicesByCommandAlias[msg.Command]
96 97
 	if !ok {
@@ -98,28 +99,60 @@ func serviceCmdHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb
98 99
 		return false
99 100
 	}
100 101
 
101
-	fakePrivmsg := strings.Join(msg.Params, " ")
102
-	servicePrivmsgHandler(service, server, client, fakePrivmsg, rb)
102
+	if len(msg.Params) == 0 {
103
+		return false
104
+	}
105
+	commandName := strings.ToLower(msg.Params[0])
106
+	params := msg.Params[1:]
107
+	cmd := lookupServiceCommand(service.Commands, commandName)
108
+	// for a maxParams command, join all final parameters together if necessary
109
+	if cmd != nil && cmd.maxParams != 0 && cmd.maxParams < len(params) {
110
+		newParams := make([]string, cmd.maxParams)
111
+		copy(newParams, params[:cmd.maxParams-1])
112
+		newParams[cmd.maxParams-1] = strings.Join(params[cmd.maxParams-1:], " ")
113
+		params = newParams
114
+	}
115
+	serviceRunCommand(service, server, client, cmd, commandName, params, rb)
103 116
 	return false
104 117
 }
105 118
 
106
-// generic handler for service PRIVMSG
119
+// generic handler for service PRIVMSG, like `/msg NickServ INFO`
107 120
 func servicePrivmsgHandler(service *ircService, server *Server, client *Client, message string, rb *ResponseBuffer) {
108
-	commandName, params := utils.ExtractParam(message)
109
-	commandName = strings.ToLower(commandName)
121
+	params := strings.Fields(message)
122
+	if len(params) == 0 {
123
+		return
124
+	}
125
+
126
+	// look up the service command to see how to parse it
127
+	commandName := strings.ToLower(params[0])
128
+	cmd := lookupServiceCommand(service.Commands, commandName)
129
+	// reparse if needed
130
+	if cmd != nil && cmd.maxParams != 0 {
131
+		params = utils.FieldsN(message, cmd.maxParams+1)[1:]
132
+	} else {
133
+		params = params[1:]
134
+	}
135
+	serviceRunCommand(service, server, client, cmd, commandName, params, rb)
136
+}
110 137
 
138
+// actually execute a service command
139
+func serviceRunCommand(service *ircService, server *Server, client *Client, cmd *serviceCommand, commandName string, params []string, rb *ResponseBuffer) {
111 140
 	nick := rb.target.Nick()
112 141
 	sendNotice := func(notice string) {
113 142
 		rb.Add(nil, service.Name, "NOTICE", nick, notice)
114 143
 	}
115 144
 
116
-	cmd := lookupServiceCommand(service.Commands, commandName)
117 145
 	if cmd == nil {
118 146
 		sendNotice(fmt.Sprintf("%s /%s HELP", client.t("Unknown command. To see available commands, run"), service.ShortName))
119 147
 		return
120 148
 	}
121 149
 
122
-	if cmd.enabled != nil && !cmd.enabled(server) {
150
+	if len(params) < cmd.minParams {
151
+		sendNotice(fmt.Sprintf(client.t("Invalid parameters. For usage, do /msg %s HELP %s"), service.Name, strings.ToUpper(commandName)))
152
+		return
153
+	}
154
+
155
+	if cmd.enabled != nil && !cmd.enabled(server.Config()) {
123 156
 		sendNotice(client.t("This command has been disabled by the server administrators"))
124 157
 		return
125 158
 	}
@@ -143,15 +176,16 @@ func servicePrivmsgHandler(service *ircService, server *Server, client *Client,
143 176
 }
144 177
 
145 178
 // generic handler that displays help for service commands
146
-func serviceHelpHandler(service *ircService, server *Server, client *Client, params string, rb *ResponseBuffer) {
179
+func serviceHelpHandler(service *ircService, server *Server, client *Client, params []string, rb *ResponseBuffer) {
147 180
 	nick := rb.target.Nick()
181
+	config := server.Config()
148 182
 	sendNotice := func(notice string) {
149 183
 		rb.Add(nil, service.Name, "NOTICE", nick, notice)
150 184
 	}
151 185
 
152 186
 	sendNotice(ircfmt.Unescape(fmt.Sprintf("*** $b%s HELP$b ***", service.Name)))
153 187
 
154
-	if params == "" {
188
+	if len(params) == 0 {
155 189
 		// show general help
156 190
 		var shownHelpLines sort.StringSlice
157 191
 		var disabledCommands bool
@@ -163,7 +197,7 @@ func serviceHelpHandler(service *ircService, server *Server, client *Client, par
163 197
 			if commandInfo.aliasOf != "" {
164 198
 				continue // don't show help lines for aliases
165 199
 			}
166
-			if commandInfo.enabled != nil && !commandInfo.enabled(server) {
200
+			if commandInfo.enabled != nil && !commandInfo.enabled(config) {
167 201
 				disabledCommands = true
168 202
 				continue
169 203
 			}
@@ -187,7 +221,7 @@ func serviceHelpHandler(service *ircService, server *Server, client *Client, par
187 221
 			sendNotice(line)
188 222
 		}
189 223
 	} else {
190
-		commandName := strings.ToLower(strings.TrimSpace(params))
224
+		commandName := strings.ToLower(params[0])
191 225
 		commandInfo := lookupServiceCommand(service.Commands, commandName)
192 226
 		if commandInfo == nil {
193 227
 			sendNotice(client.t(fmt.Sprintf("Unknown command. To see available commands, run /%s HELP", service.ShortName)))

+ 0
- 13
irc/utils/args.go View File

@@ -3,19 +3,6 @@
3 3
 
4 4
 package utils
5 5
 
6
-import "strings"
7
-
8
-// ExtractParam extracts a parameter from the given string, returning the param and the rest of the string.
9
-func ExtractParam(line string) (string, string) {
10
-	rawParams := strings.SplitN(strings.TrimSpace(line), " ", 2)
11
-	param0 := rawParams[0]
12
-	var param1 string
13
-	if 1 < len(rawParams) {
14
-		param1 = strings.TrimSpace(rawParams[1])
15
-	}
16
-	return param0, param1
17
-}
18
-
19 6
 // ArgsToStrings takes the arguments and splits them into a series of strings,
20 7
 // each argument separated by delim and each string bounded by maxLength.
21 8
 func ArgsToStrings(maxLength int, arguments []string, delim string) []string {

+ 52
- 0
irc/utils/fieldsn.go View File

@@ -0,0 +1,52 @@
1
+package utils
2
+
3
+// Copyright (c) 2014 Kevin Wallace <kevin@pentabarf.net>
4
+// Found here: https://github.com/kevinwallace/fieldsn
5
+// Released under the MIT license
6
+// XXX this implementation treats negative n as "return nil",
7
+// unlike stdlib SplitN and friends, which treat it as "no limit"
8
+
9
+// Original source code below:
10
+
11
+// Package fieldsn implements FieldsN and FieldsFuncN,
12
+// which are conspicuously missing from the strings package.
13
+
14
+import (
15
+	"unicode"
16
+)
17
+
18
+// FieldsN is like strings.Fields, but returns at most n fields,
19
+// and the nth field includes any whitespace at the end of the string.
20
+func FieldsN(s string, n int) []string {
21
+	return FieldsFuncN(s, unicode.IsSpace, n)
22
+}
23
+
24
+// FieldsFuncN is like strings.FieldsFunc, but returns at most n fields,
25
+// and the nth field includes any runes at the end of the string normally excluded by f.
26
+func FieldsFuncN(s string, f func(rune) bool, n int) []string {
27
+	if n <= 0 {
28
+		return nil
29
+	}
30
+
31
+	a := make([]string, 0, n)
32
+	na := 0
33
+	fieldStart := -1
34
+	for i, rune := range s {
35
+		if f(rune) {
36
+			if fieldStart >= 0 {
37
+				a = append(a, s[fieldStart:i])
38
+				na++
39
+				fieldStart = -1
40
+			}
41
+		} else if fieldStart == -1 {
42
+			fieldStart = i
43
+			if na+1 == n {
44
+				break
45
+			}
46
+		}
47
+	}
48
+	if fieldStart >= 0 {
49
+		a = append(a, s[fieldStart:])
50
+	}
51
+	return a
52
+}

Loading…
Cancel
Save