1// Copyright (c) 2012-2014 Jeremy Latt
2// Copyright (c) 2014-2015 Edmund Huber
3// Copyright (c) 2016-2018 Daniel Oaks <daniel@danieloaks.net>
4// Copyright (c) 2017-2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
5// released under the MIT license
6
7package irc
8
9import (
10	"bytes"
11	"encoding/base64"
12	"fmt"
13	"net"
14	"os"
15	"runtime"
16	"runtime/debug"
17	"runtime/pprof"
18	"sort"
19	"strconv"
20	"strings"
21	"time"
22
23	"github.com/ergochat/irc-go/ircfmt"
24	"github.com/ergochat/irc-go/ircmsg"
25	"github.com/ergochat/irc-go/ircutils"
26	"golang.org/x/crypto/bcrypt"
27
28	"github.com/ergochat/ergo/irc/caps"
29	"github.com/ergochat/ergo/irc/custime"
30	"github.com/ergochat/ergo/irc/flatip"
31	"github.com/ergochat/ergo/irc/history"
32	"github.com/ergochat/ergo/irc/jwt"
33	"github.com/ergochat/ergo/irc/modes"
34	"github.com/ergochat/ergo/irc/sno"
35	"github.com/ergochat/ergo/irc/utils"
36)
37
38// helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234
39func parseCallback(spec string, config *Config) (callbackNamespace string, callbackValue string, err error) {
40	// XXX if we don't require verification, ignore any callback that was passed here
41	// (to avoid confusion in the case where the ircd has no mail server configured)
42	if !config.Accounts.Registration.EmailVerification.Enabled {
43		callbackNamespace = "*"
44		return
45	}
46	callback := strings.ToLower(spec)
47	if colonIndex := strings.IndexByte(callback, ':'); colonIndex != -1 {
48		callbackNamespace, callbackValue = callback[:colonIndex], callback[colonIndex+1:]
49	} else {
50		// "If a callback namespace is not ... provided, the IRC server MUST use mailto""
51		callbackNamespace = "mailto"
52		callbackValue = callback
53	}
54
55	if config.Accounts.Registration.EmailVerification.Enabled {
56		if callbackNamespace != "mailto" {
57			err = errValidEmailRequired
58		} else if strings.IndexByte(callbackValue, '@') < 1 {
59			err = errValidEmailRequired
60		}
61	}
62
63	return
64}
65
66func registrationErrorToMessage(config *Config, client *Client, err error) (message string) {
67	if emailError := registrationCallbackErrorText(config, client, err); emailError != "" {
68		return emailError
69	}
70
71	switch err {
72	case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered, errAccountAlreadyLoggedIn, errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled, errAccountBadPassphrase:
73		message = err.Error()
74	case errLimitExceeded:
75		message = `There have been too many registration attempts recently; try again later`
76	default:
77		// default response: let's be risk-averse about displaying internal errors
78		// to the clients, especially for something as sensitive as accounts
79		message = `Could not register`
80	}
81	return
82}
83
84// helper function to dispatch messages when a client successfully registers
85func sendSuccessfulRegResponse(service *ircService, client *Client, rb *ResponseBuffer) {
86	details := client.Details()
87	if service != nil {
88		service.Notice(rb, client.t("Account created"))
89	} else {
90		rb.Add(nil, client.server.name, RPL_REG_SUCCESS, details.nick, details.accountName, client.t("Account created"))
91	}
92	client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] registered account $c[grey][$r%s$c[grey]] from IP %s"), details.nickMask, details.accountName, rb.session.IP().String()))
93	sendSuccessfulAccountAuth(service, client, rb, false)
94}
95
96// sendSuccessfulAccountAuth means that an account auth attempt completed successfully, and is used to dispatch messages.
97func sendSuccessfulAccountAuth(service *ircService, client *Client, rb *ResponseBuffer, forSASL bool) {
98	details := client.Details()
99
100	if service != nil {
101		service.Notice(rb, fmt.Sprintf(client.t("You're now logged in as %s"), details.accountName))
102	} else {
103		//TODO(dan): some servers send this numeric even for NickServ logins iirc? to confirm and maybe do too
104		rb.Add(nil, client.server.name, RPL_LOGGEDIN, details.nick, details.nickMask, details.accountName, fmt.Sprintf(client.t("You are now logged in as %s"), details.accountName))
105		if forSASL {
106			rb.Add(nil, client.server.name, RPL_SASLSUCCESS, details.nick, client.t("Authentication successful"))
107		}
108	}
109
110	if client.Registered() {
111		// dispatch account-notify
112		for friend := range client.FriendsMonitors(caps.AccountNotify) {
113			if friend != rb.session {
114				friend.Send(nil, details.nickMask, "ACCOUNT", details.accountName)
115			}
116		}
117		if rb.session.capabilities.Has(caps.AccountNotify) {
118			rb.Add(nil, details.nickMask, "ACCOUNT", details.accountName)
119		}
120		client.server.sendLoginSnomask(details.nickMask, details.accountName)
121	}
122
123	// #1479: for Tor clients, replace the hostname with the always-on cloak here
124	// (for normal clients, this would discard the IP-based cloak, but with Tor
125	// there's no such concern)
126	if rb.session.isTor {
127		config := client.server.Config()
128		if config.Server.Cloaks.EnabledForAlwaysOn {
129			cloakedHostname := config.Server.Cloaks.ComputeAccountCloak(details.accountName)
130			client.setCloakedHostname(cloakedHostname)
131			if client.registered {
132				client.sendChghost(details.nickMask, client.Hostname())
133			}
134		}
135	}
136
137	client.server.logger.Info("accounts", "client", details.nick, "logged into account", details.accountName)
138}
139
140func (server *Server) sendLoginSnomask(nickMask, accountName string) {
141	server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]"), nickMask, accountName))
142}
143
144// AUTHENTICATE [<mechanism>|<data>|*]
145func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
146	session := rb.session
147	config := server.Config()
148	details := client.Details()
149
150	if client.isSTSOnly {
151		rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
152		return false
153	}
154
155	if details.account != "" {
156		rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
157		return false
158	}
159
160	// sasl abort
161	if !config.Accounts.AuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
162		rb.Add(nil, server.name, ERR_SASLABORTED, details.nick, client.t("SASL authentication aborted"))
163		session.sasl.Clear()
164		return false
165	}
166
167	// start new sasl session
168	if session.sasl.mechanism == "" {
169		throttled, remainingTime := client.loginThrottle.Touch()
170		if throttled {
171			rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
172			return false
173		}
174
175		mechanism := strings.ToUpper(msg.Params[0])
176		_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]
177
178		if mechanismIsEnabled {
179			session.sasl.mechanism = mechanism
180			if !config.Server.Compatibility.SendUnprefixedSasl {
181				// normal behavior
182				rb.Add(nil, server.name, "AUTHENTICATE", "+")
183			} else {
184				// gross hack: send a raw message to ensure no tags or prefix
185				rb.Flush(true)
186				rb.session.SendRawMessage(ircmsg.MakeMessage(nil, "", "AUTHENTICATE", "+"), true)
187			}
188		} else {
189			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
190		}
191
192		return false
193	}
194
195	// continue existing sasl session
196	rawData := msg.Params[0]
197
198	if len(rawData) > 400 {
199		rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
200		session.sasl.Clear()
201		return false
202	} else if len(rawData) == 400 {
203		// allow 4 'continuation' lines before rejecting for length
204		if len(session.sasl.value) >= 400*4 {
205			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
206			session.sasl.Clear()
207			return false
208		}
209		session.sasl.value += rawData
210		return false
211	}
212	if rawData != "+" {
213		session.sasl.value += rawData
214	}
215
216	var data []byte
217	var err error
218	if session.sasl.value != "+" {
219		data, err = base64.StdEncoding.DecodeString(session.sasl.value)
220		session.sasl.value = ""
221		if err != nil {
222			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
223			session.sasl.Clear()
224			return false
225		}
226	}
227
228	// call actual handler
229	handler := EnabledSaslMechanisms[session.sasl.mechanism]
230	return handler(server, client, session, data, rb)
231}
232
233// AUTHENTICATE PLAIN
234func authPlainHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
235	defer session.sasl.Clear()
236
237	splitValue := bytes.Split(value, []byte{'\000'})
238
239	// PLAIN has separate "authorization ID" (which user you want to become)
240	// and "authentication ID" (whose password you want to use). the first is optional:
241	// [authzid] \x00 authcid \x00 password
242	var authzid, authcid string
243
244	if len(splitValue) == 3 {
245		authzid, authcid = string(splitValue[0]), string(splitValue[1])
246
247		if authzid != "" && authcid != authzid {
248			rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), client.t("SASL authentication failed: authcid and authzid should be the same"))
249			return false
250		}
251	} else {
252		rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), client.t("SASL authentication failed: Invalid auth blob"))
253		return false
254	}
255
256	// see #843: strip the device ID for the benefit of clients that don't
257	// distinguish user/ident from account name
258	if strudelIndex := strings.IndexByte(authcid, '@'); strudelIndex != -1 {
259		var deviceID string
260		authcid, deviceID = authcid[:strudelIndex], authcid[strudelIndex+1:]
261		if !client.registered {
262			rb.session.deviceID = deviceID
263		}
264	}
265	password := string(splitValue[2])
266	err := server.accounts.AuthenticateByPassphrase(client, authcid, password)
267	if err != nil {
268		msg := authErrorToMessage(server, err)
269		rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
270		return false
271	} else if !fixupNickEqualsAccount(client, rb, server.Config(), "") {
272		return false
273	}
274
275	sendSuccessfulAccountAuth(nil, client, rb, true)
276	return false
277}
278
279func authErrorToMessage(server *Server, err error) (msg string) {
280	if throttled, ok := err.(*ThrottleError); ok {
281		return throttled.Error()
282	}
283
284	switch err {
285	case errAccountDoesNotExist, errAccountUnverified, errAccountInvalidCredentials, errAuthzidAuthcidMismatch, errNickAccountMismatch, errAccountSuspended:
286		return err.Error()
287	default:
288		// don't expose arbitrary error messages to the user
289		server.logger.Error("internal", "sasl authentication failure", err.Error())
290		return "Unknown"
291	}
292}
293
294// AUTHENTICATE EXTERNAL
295func authExternalHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
296	defer session.sasl.Clear()
297
298	if rb.session.certfp == "" {
299		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed, you are not connecting with a certificate"))
300		return false
301	}
302
303	// EXTERNAL doesn't carry an authentication ID (this is determined from the
304	// certificate), but does carry an optional authorization ID.
305	var authzid string
306	var err error
307	if len(value) != 0 {
308		authzid, err = CasefoldName(string(value))
309		if err != nil {
310			err = errAuthzidAuthcidMismatch
311		}
312	}
313
314	if err == nil {
315		// see #843: strip the device ID for the benefit of clients that don't
316		// distinguish user/ident from account name
317		if strudelIndex := strings.IndexByte(authzid, '@'); strudelIndex != -1 {
318			var deviceID string
319			authzid, deviceID = authzid[:strudelIndex], authzid[strudelIndex+1:]
320			if !client.registered {
321				rb.session.deviceID = deviceID
322			}
323		}
324		err = server.accounts.AuthenticateByCertificate(client, rb.session.certfp, rb.session.peerCerts, authzid)
325	}
326
327	if err != nil {
328		msg := authErrorToMessage(server, err)
329		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
330		return false
331	} else if !fixupNickEqualsAccount(client, rb, server.Config(), "") {
332		return false
333	}
334
335	sendSuccessfulAccountAuth(nil, client, rb, true)
336	return false
337}
338
339// AUTHENTICATE SCRAM-SHA-256
340func authScramHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
341	continueAuth := true
342	defer func() {
343		if !continueAuth {
344			session.sasl.Clear()
345		}
346	}()
347
348	// first message? if so, initialize the SCRAM conversation
349	if session.sasl.scramConv == nil {
350		session.sasl.scramConv = server.accounts.NewScramConversation()
351	}
352
353	// wait for a final AUTHENTICATE + from the client to conclude authentication
354	if session.sasl.scramConv.Done() {
355		continueAuth = false
356		if session.sasl.scramConv.Valid() {
357			authcid := session.sasl.scramConv.Username()
358			if strudelIndex := strings.IndexByte(authcid, '@'); strudelIndex != -1 {
359				var deviceID string
360				authcid, deviceID = authcid[:strudelIndex], authcid[strudelIndex+1:]
361				if !client.registered {
362					rb.session.deviceID = deviceID
363				}
364			}
365			authzid := session.sasl.scramConv.AuthzID()
366			if authzid != "" && authzid != authcid {
367				rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed: authcid and authzid should be the same"))
368				return false
369			}
370			account, err := server.accounts.LoadAccount(authcid)
371			if err == nil {
372				server.accounts.Login(client, account)
373				if fixupNickEqualsAccount(client, rb, server.Config(), "") {
374					sendSuccessfulAccountAuth(nil, client, rb, true)
375				}
376			} else {
377				server.logger.Error("internal", "SCRAM succeeded but couldn't load account", authcid, err.Error())
378				rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
379			}
380		} else {
381			rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
382		}
383		return false
384	}
385
386	response, err := session.sasl.scramConv.Step(string(value))
387	if err == nil {
388		rb.Add(nil, server.name, "AUTHENTICATE", base64.StdEncoding.EncodeToString([]byte(response)))
389	} else {
390		continueAuth = false
391		rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), err.Error())
392		return false
393	}
394
395	return false
396}
397
398// AWAY [<message>]
399func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
400	var isAway bool
401	var awayMessage string
402	if len(msg.Params) > 0 {
403		isAway = true
404		awayMessage = msg.Params[0]
405		awayMessage = ircutils.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen)
406	}
407
408	rb.session.SetAway(awayMessage)
409
410	if isAway {
411		rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
412	} else {
413		rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
414	}
415
416	dispatchAwayNotify(client, isAway, awayMessage)
417	return false
418}
419
420func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
421	// dispatch away-notify
422	details := client.Details()
423	isBot := client.HasMode(modes.Bot)
424	for session := range client.FriendsMonitors(caps.AwayNotify) {
425		if isAway {
426			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
427		} else {
428			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")
429		}
430	}
431}
432
433// BATCH {+,-}reference-tag type [params...]
434func batchHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
435	tag := msg.Params[0]
436	fail := false
437	sendErrors := rb.session.batch.command != "NOTICE"
438	if len(tag) == 0 {
439		fail = true
440	} else if tag[0] == '+' {
441		if len(msg.Params) < 3 || msg.Params[1] != caps.MultilineBatchType {
442			fail = true
443		} else {
444			err := rb.session.StartMultilineBatch(tag[1:], msg.Params[2], rb.Label, msg.ClientOnlyTags())
445			fail = (err != nil)
446			if !fail {
447				// suppress ACK for the initial BATCH message (we'll apply the stored label later)
448				rb.Label = ""
449			}
450		}
451	} else if tag[0] == '-' {
452		batch, err := rb.session.EndMultilineBatch(tag[1:])
453		fail = (err != nil)
454		if !fail {
455			histType, err := msgCommandToHistType(batch.command)
456			if err != nil {
457				histType = history.Privmsg
458				batch.command = "PRIVMSG"
459			}
460			// XXX changing the label inside a handler is a bit dodgy, but it works here
461			// because there's no way we could have triggered a flush up to this point
462			rb.Label = batch.responseLabel
463			dispatchMessageToTarget(client, batch.tags, histType, batch.command, batch.target, batch.message, rb)
464		}
465	}
466
467	if fail {
468		rb.session.EndMultilineBatch("")
469		if sendErrors {
470			rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_INVALID", client.t("Invalid multiline batch"))
471		}
472	}
473
474	return false
475}
476
477// CAP <subcmd> [<caps>]
478func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
479	details := client.Details()
480	subCommand := strings.ToUpper(msg.Params[0])
481	toAdd := caps.NewSet()
482	toRemove := caps.NewSet()
483	var capString string
484
485	config := server.Config()
486	supportedCaps := config.Server.supportedCaps
487	if client.isSTSOnly {
488		supportedCaps = stsOnlyCaps
489	} else if rb.session.hideSTS {
490		supportedCaps = config.Server.supportedCapsWithoutSTS
491	}
492
493	badCaps := false
494	if len(msg.Params) > 1 {
495		capString = msg.Params[1]
496		strs := strings.Fields(capString)
497		for _, str := range strs {
498			remove := false
499			if str[0] == '-' {
500				str = str[1:]
501				remove = true
502			}
503			capab, err := caps.NameToCapability(str)
504			if err != nil || (!remove && !supportedCaps.Has(capab)) {
505				badCaps = true
506			} else if !remove {
507				toAdd.Enable(capab)
508			} else {
509				toRemove.Enable(capab)
510			}
511		}
512	}
513
514	sendCapLines := func(cset *caps.Set, values caps.Values) {
515		version := rb.session.capVersion
516		// we're working around two bugs:
517		// 1. WeeChat 1.4 won't accept the CAP reply unless it contains the server.name source
518		// 2. old versions of Kiwi and The Lounge can't parse multiline CAP LS 302 (#661),
519		// so try as hard as possible to get the response to fit on one line.
520		// :server.name CAP * LS * :<tokens>\r\n
521		// 1           [ 7   ]  [4 ]        [2 ]
522		maxLen := (MaxLineLen - 2) - 1 - len(server.name) - 7 - len(subCommand) - 4
523		capLines := cset.Strings(version, values, maxLen)
524		for i, capStr := range capLines {
525			if version >= caps.Cap302 && i < len(capLines)-1 {
526				rb.Add(nil, server.name, "CAP", details.nick, subCommand, "*", capStr)
527			} else {
528				rb.Add(nil, server.name, "CAP", details.nick, subCommand, capStr)
529			}
530		}
531	}
532
533	switch subCommand {
534	case "LS":
535		if !client.registered {
536			rb.session.capState = caps.NegotiatingState
537		}
538		if 1 < len(msg.Params) {
539			num, err := strconv.Atoi(msg.Params[1])
540			newVersion := caps.Version(num)
541			if err == nil && rb.session.capVersion < newVersion {
542				rb.session.capVersion = newVersion
543			}
544		}
545		sendCapLines(supportedCaps, config.Server.capValues)
546
547	case "LIST":
548		// values not sent on LIST
549		sendCapLines(&rb.session.capabilities, nil)
550
551	case "REQ":
552		if !client.registered {
553			rb.session.capState = caps.NegotiatingState
554		}
555
556		// make sure all capabilities actually exist
557		// #511, #521: oragono.io/nope is a fake cap to trap bad clients who blindly request
558		// every offered capability. during registration, requesting it produces a quit,
559		// otherwise just a CAP NAK
560		if badCaps || (toAdd.Has(caps.Nope) && client.registered) {
561			rb.Add(nil, server.name, "CAP", details.nick, "NAK", capString)
562			return false
563		} else if toAdd.Has(caps.Nope) && !client.registered {
564			client.Quit(fmt.Sprintf(client.t("Requesting the %s client capability is forbidden"), caps.Nope.Name()), rb.session)
565			return true
566		}
567
568		rb.session.capabilities.Union(toAdd)
569		rb.session.capabilities.Subtract(toRemove)
570		rb.Add(nil, server.name, "CAP", details.nick, "ACK", capString)
571
572	case "END":
573		if !client.registered {
574			rb.session.capState = caps.NegotiatedState
575		}
576
577	default:
578		rb.Add(nil, server.name, ERR_INVALIDCAPCMD, details.nick, subCommand, client.t("Invalid CAP subcommand"))
579	}
580	return false
581}
582
583// CHATHISTORY <target> <preposition> <query> [<limit>]
584// e.g., CHATHISTORY #ircv3 AFTER id=ytNBbt565yt4r3err3 10
585// CHATHISTORY <target> BETWEEN <query> <query> <direction> [<limit>]
586// e.g., CHATHISTORY #ircv3 BETWEEN timestamp=YYYY-MM-DDThh:mm:ss.sssZ timestamp=YYYY-MM-DDThh:mm:ss.sssZ + 100
587func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) (exiting bool) {
588	var items []history.Item
589	var target string
590	var channel *Channel
591	var sequence history.Sequence
592	var err error
593	var listTargets bool
594	var targets []history.TargetListing
595	defer func() {
596		// errors are sent either without a batch, or in a draft/labeled-response batch as usual
597		if err == utils.ErrInvalidParams {
598			rb.Add(nil, server.name, "FAIL", "CHATHISTORY", "INVALID_PARAMS", msg.Params[0], client.t("Invalid parameters"))
599		} else if !listTargets && sequence == nil {
600			rb.Add(nil, server.name, "FAIL", "CHATHISTORY", "INVALID_TARGET", msg.Params[0], utils.SafeErrorParam(target), client.t("Messages could not be retrieved"))
601		} else if err != nil {
602			rb.Add(nil, server.name, "FAIL", "CHATHISTORY", "MESSAGE_ERROR", msg.Params[0], client.t("Messages could not be retrieved"))
603		} else {
604			// successful responses are sent as a chathistory or history batch
605			if listTargets {
606				batchID := rb.StartNestedBatch("draft/chathistory-targets")
607				defer rb.EndNestedBatch(batchID)
608				for _, target := range targets {
609					name := server.UnfoldName(target.CfName)
610					rb.Add(nil, server.name, "CHATHISTORY", "TARGETS", name,
611						target.Time.Format(IRCv3TimestampFormat))
612				}
613			} else if channel != nil {
614				channel.replayHistoryItems(rb, items, true)
615			} else {
616				client.replayPrivmsgHistory(rb, items, target, true)
617			}
618		}
619	}()
620
621	config := server.Config()
622	maxChathistoryLimit := config.History.ChathistoryMax
623	if maxChathistoryLimit == 0 {
624		return
625	}
626	preposition := strings.ToLower(msg.Params[0])
627	target = msg.Params[1]
628	listTargets = (preposition == "targets")
629
630	parseQueryParam := func(param string) (msgid string, timestamp time.Time, err error) {
631		if param == "*" && (preposition == "before" || preposition == "between") {
632			// XXX compatibility with kiwi, which as of February 2020 is
633			// using BEFORE * as a synonym for LATEST *
634			return
635		}
636		err = utils.ErrInvalidParams
637		pieces := strings.SplitN(param, "=", 2)
638		if len(pieces) < 2 {
639			return
640		}
641		identifier, value := strings.ToLower(pieces[0]), pieces[1]
642		if identifier == "msgid" {
643			msgid, err = history.NormalizeMsgid(value), nil
644			return
645		} else if identifier == "timestamp" {
646			timestamp, err = time.Parse(IRCv3TimestampFormat, value)
647			return
648		}
649		return
650	}
651
652	parseHistoryLimit := func(paramIndex int) (limit int) {
653		if len(msg.Params) < (paramIndex + 1) {
654			return maxChathistoryLimit
655		}
656		limit, err := strconv.Atoi(msg.Params[paramIndex])
657		if err != nil || limit == 0 || limit > maxChathistoryLimit {
658			limit = maxChathistoryLimit
659		}
660		return
661	}
662
663	roundUp := func(endpoint time.Time) (result time.Time) {
664		return endpoint.Truncate(time.Millisecond).Add(time.Millisecond)
665	}
666
667	paramPos := 2
668	var start, end history.Selector
669	var limit int
670	switch preposition {
671	case "targets":
672		// use the same selector parsing as BETWEEN,
673		// except that we have no target so we have one fewer parameter
674		paramPos = 1
675		fallthrough
676	case "between":
677		start.Msgid, start.Time, err = parseQueryParam(msg.Params[paramPos])
678		if err != nil {
679			return
680		}
681		end.Msgid, end.Time, err = parseQueryParam(msg.Params[paramPos+1])
682		if err != nil {
683			return
684		}
685		// XXX preserve the ordering of the two parameters, since we might be going backwards,
686		// but round up the chronologically first one, whichever it is, to make it exclusive
687		if !start.Time.IsZero() && !end.Time.IsZero() {
688			if start.Time.Before(end.Time) {
689				start.Time = roundUp(start.Time)
690			} else {
691				end.Time = roundUp(end.Time)
692			}
693		}
694		limit = parseHistoryLimit(paramPos + 2)
695	case "before", "after", "around":
696		start.Msgid, start.Time, err = parseQueryParam(msg.Params[2])
697		if err != nil {
698			return
699		}
700		if preposition == "after" && !start.Time.IsZero() {
701			start.Time = roundUp(start.Time)
702		}
703		if preposition == "before" {
704			end = start
705			start = history.Selector{}
706		}
707		limit = parseHistoryLimit(3)
708	case "latest":
709		if msg.Params[2] != "*" {
710			end.Msgid, end.Time, err = parseQueryParam(msg.Params[2])
711			if err != nil {
712				return
713			}
714			if !end.Time.IsZero() {
715				end.Time = roundUp(end.Time)
716			}
717			start.Time = time.Now().UTC()
718		}
719		limit = parseHistoryLimit(3)
720	default:
721		err = utils.ErrInvalidParams
722		return
723	}
724
725	if listTargets {
726		targets, err = client.listTargets(start, end, limit)
727	} else {
728		channel, sequence, err = server.GetHistorySequence(nil, client, target)
729		if err != nil || sequence == nil {
730			return
731		}
732		if preposition == "around" {
733			items, err = sequence.Around(start, limit)
734		} else {
735			items, err = sequence.Between(start, end, limit)
736		}
737	}
738	return
739}
740
741// DEBUG <subcmd>
742func debugHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
743	param := strings.ToUpper(msg.Params[0])
744
745	switch param {
746	case "GCSTATS":
747		stats := debug.GCStats{
748			Pause:          make([]time.Duration, 10),
749			PauseQuantiles: make([]time.Duration, 5),
750		}
751		debug.ReadGCStats(&stats)
752
753		rb.Notice(fmt.Sprintf("last GC:     %s", stats.LastGC.Format(time.RFC1123)))
754		rb.Notice(fmt.Sprintf("num GC:      %d", stats.NumGC))
755		rb.Notice(fmt.Sprintf("pause total: %s", stats.PauseTotal))
756		rb.Notice(fmt.Sprintf("pause quantiles min%%: %s", stats.PauseQuantiles[0]))
757		rb.Notice(fmt.Sprintf("pause quantiles 25%%:  %s", stats.PauseQuantiles[1]))
758		rb.Notice(fmt.Sprintf("pause quantiles 50%%:  %s", stats.PauseQuantiles[2]))
759		rb.Notice(fmt.Sprintf("pause quantiles 75%%:  %s", stats.PauseQuantiles[3]))
760		rb.Notice(fmt.Sprintf("pause quantiles max%%: %s", stats.PauseQuantiles[4]))
761
762	case "NUMGOROUTINE":
763		count := runtime.NumGoroutine()
764		rb.Notice(fmt.Sprintf("num goroutines: %d", count))
765
766	case "PROFILEHEAP":
767		profFile := server.Config().getOutputPath("ergo.mprof")
768		file, err := os.Create(profFile)
769		if err != nil {
770			rb.Notice(fmt.Sprintf("error: %s", err))
771			break
772		}
773		defer file.Close()
774		pprof.Lookup("heap").WriteTo(file, 0)
775		rb.Notice(fmt.Sprintf("written to %s", profFile))
776
777	case "STARTCPUPROFILE":
778		profFile := server.Config().getOutputPath("ergo.prof")
779		file, err := os.Create(profFile)
780		if err != nil {
781			rb.Notice(fmt.Sprintf("error: %s", err))
782			break
783		}
784		if err := pprof.StartCPUProfile(file); err != nil {
785			defer file.Close()
786			rb.Notice(fmt.Sprintf("error: %s", err))
787			break
788		}
789
790		rb.Notice(fmt.Sprintf("CPU profile writing to %s", profFile))
791
792	case "STOPCPUPROFILE":
793		pprof.StopCPUProfile()
794		rb.Notice(fmt.Sprintf("CPU profiling stopped"))
795
796	case "CRASHSERVER":
797		code := utils.ConfirmationCode(server.name, server.ctime)
798		if len(msg.Params) == 1 || msg.Params[1] != code {
799			rb.Notice(fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/DEBUG CRASHSERVER %s", code)))
800			return false
801		}
802		server.logger.Error("server", fmt.Sprintf("DEBUG CRASHSERVER executed by operator %s", client.Oper().Name))
803		go func() {
804			// intentional nil dereference on a new goroutine, bypassing recover-from-errors
805			var i, j *int
806			*i = *j
807		}()
808
809	default:
810		rb.Notice(client.t("Unrecognized DEBUG subcommand"))
811	}
812	return false
813}
814
815func defconHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
816	if len(msg.Params) > 0 {
817		level, err := strconv.Atoi(msg.Params[0])
818		if err == nil && 1 <= level && level <= 5 {
819			server.SetDefcon(uint32(level))
820			server.snomasks.Send(sno.LocalAnnouncements, fmt.Sprintf("%s [%s] set DEFCON level to %d", client.Nick(), client.Oper().Name, level))
821		} else {
822			rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, client.t("Invalid DEFCON parameter"))
823			return false
824		}
825	}
826	rb.Notice(fmt.Sprintf(client.t("Current DEFCON level is %d"), server.Defcon()))
827	return false
828}
829
830// helper for parsing the reason args to DLINE and KLINE
831func getReasonsFromParams(params []string, currentArg int) (reason, operReason string) {
832	reason = "No reason given"
833	operReason = ""
834	if len(params) > currentArg {
835		reasons := strings.SplitN(strings.Join(params[currentArg:], " "), "|", 2)
836		if len(reasons) == 1 {
837			reason = strings.TrimSpace(reasons[0])
838		} else if len(reasons) == 2 {
839			reason = strings.TrimSpace(reasons[0])
840			operReason = strings.TrimSpace(reasons[1])
841		}
842	}
843	return
844}
845
846func formatBanForListing(client *Client, key string, info IPBanInfo) string {
847	desc := info.Reason
848	if info.OperReason != "" && info.OperReason != info.Reason {
849		desc = fmt.Sprintf("%s | %s", info.Reason, info.OperReason)
850	}
851	if info.Duration != 0 {
852		desc = fmt.Sprintf("%s [%s]", desc, info.TimeLeft())
853	}
854	desc = fmt.Sprintf("%s added on [%s]", desc, info.TimeCreated.UTC().Format(time.RFC1123))
855	banType := "Ban"
856	if info.RequireSASL {
857		banType = "SASL required"
858	}
859	return fmt.Sprintf(client.t("%[1]s - %[2]s - added by %[3]s - %[4]s"), banType, key, info.OperName, desc)
860}
861
862// DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
863// DLINE LIST
864func dlineHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
865	// check oper permissions
866	oper := client.Oper()
867	if !oper.HasRoleCapab("ban") {
868		rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs"))
869		return false
870	}
871
872	currentArg := 0
873
874	// if they say LIST, we just list the current dlines
875	if len(msg.Params) == currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "list" {
876		bans := server.dlines.AllBans()
877
878		if len(bans) == 0 {
879			rb.Notice(client.t("No DLINEs have been set!"))
880		}
881
882		for key, info := range bans {
883			client.Notice(formatBanForListing(client, key, info))
884		}
885
886		return false
887	}
888
889	// when setting a ban, if they say "ANDKILL" we should also kill all users who match it
890	var andKill bool
891	if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "andkill" {
892		andKill = true
893		currentArg++
894	}
895
896	// when setting a ban that covers the oper's current connection, we require them to say
897	// "DLINE MYSELF" so that we're sure they really mean it.
898	var dlineMyself bool
899	if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "myself" {
900		dlineMyself = true
901		currentArg++
902	}
903
904	// duration
905	duration, err := custime.ParseDuration(msg.Params[currentArg])
906	if err != nil {
907		duration = 0
908	} else {
909		currentArg++
910	}
911
912	// get host
913	if len(msg.Params) < currentArg+1 {
914		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
915		return false
916	}
917	hostString := msg.Params[currentArg]
918	currentArg++
919
920	// check host
921	hostNet, err := utils.NormalizedNetFromString(hostString)
922
923	if err != nil {
924		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Could not parse IP address or CIDR network"))
925		return false
926	}
927
928	if !dlineMyself && hostNet.Contains(rb.session.IP()) {
929		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("This ban matches you. To DLINE yourself, you must use the command:  /DLINE MYSELF <arguments>"))
930		return false
931	}
932
933	// check remote
934	if len(msg.Params) > currentArg && msg.Params[currentArg] == "ON" {
935		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Remote servers not yet supported"))
936		return false
937	}
938
939	// get comment(s)
940	reason, operReason := getReasonsFromParams(msg.Params, currentArg)
941
942	operName := oper.Name
943	if operName == "" {
944		operName = server.name
945	}
946
947	err = server.dlines.AddNetwork(flatip.FromNetIPNet(hostNet), duration, false, reason, operReason, operName)
948
949	if err != nil {
950		rb.Notice(fmt.Sprintf(client.t("Could not successfully save new D-LINE: %s"), err.Error()))
951		return false
952	}
953
954	var snoDescription string
955	hostString = utils.NetToNormalizedString(hostNet)
956	if duration != 0 {
957		rb.Notice(fmt.Sprintf(client.t("Added temporary (%[1]s) D-Line for %[2]s"), duration.String(), hostString))
958		snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added temporary (%s) D-Line for %s"), client.nick, operName, duration.String(), hostString)
959	} else {
960		rb.Notice(fmt.Sprintf(client.t("Added D-Line for %s"), hostString))
961		snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added D-Line for %s"), client.nick, operName, hostString)
962	}
963	server.snomasks.Send(sno.LocalXline, snoDescription)
964
965	var killClient bool
966	if andKill {
967		var sessionsToKill []*Session
968		var killedClientNicks []string
969
970		for _, mcl := range server.clients.AllClients() {
971			nickKilled := false
972			for _, session := range mcl.Sessions() {
973				if hostNet.Contains(session.IP()) {
974					sessionsToKill = append(sessionsToKill, session)
975					if !nickKilled {
976						killedClientNicks = append(killedClientNicks, mcl.Nick())
977						nickKilled = true
978					}
979				}
980			}
981		}
982
983		for _, session := range sessionsToKill {
984			mcl := session.client
985			mcl.Quit(fmt.Sprintf(mcl.t("You have been banned from this server (%s)"), reason), session)
986			if session == rb.session {
987				killClient = true
988			} else {
989				// if mcl == client, we kill them below
990				mcl.destroy(session)
991			}
992		}
993
994		// send snomask
995		sort.Strings(killedClientNicks)
996		server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s [%s] killed %d clients with a DLINE $c[grey][$r%s$c[grey]]"), client.nick, operName, len(killedClientNicks), strings.Join(killedClientNicks, ", ")))
997	}
998
999	return killClient
1000}
1001
1002// EXTJWT <target> [service_name]
1003func extjwtHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1004	accountName := client.AccountName()
1005	if accountName == "*" {
1006		accountName = ""
1007	}
1008
1009	claims := jwt.MapClaims{
1010		"iss":     server.name,
1011		"sub":     client.Nick(),
1012		"account": accountName,
1013		"umodes":  []string{},
1014	}
1015
1016	if msg.Params[0] != "*" {
1017		channel := server.channels.Get(msg.Params[0])
1018		if channel == nil {
1019			rb.Add(nil, server.name, "FAIL", "EXTJWT", "NO_SUCH_CHANNEL", client.t("No such channel"))
1020			return false
1021		}
1022
1023		claims["channel"] = channel.Name()
1024		claims["joined"] = 0
1025		claims["cmodes"] = []string{}
1026		if present, joinTimeSecs, cModes := channel.ClientStatus(client); present {
1027			claims["joined"] = joinTimeSecs
1028			var modeStrings []string
1029			for _, cMode := range cModes {
1030				modeStrings = append(modeStrings, string(cMode))
1031			}
1032			claims["cmodes"] = modeStrings
1033		}
1034	}
1035
1036	config := server.Config()
1037	var serviceName string
1038	var sConfig jwt.JwtServiceConfig
1039	if 1 < len(msg.Params) {
1040		serviceName = strings.ToLower(msg.Params[1])
1041		sConfig = config.Extjwt.Services[serviceName]
1042	} else {
1043		serviceName = "*"
1044		sConfig = config.Extjwt.Default
1045	}
1046
1047	if !sConfig.Enabled() {
1048		rb.Add(nil, server.name, "FAIL", "EXTJWT", "NO_SUCH_SERVICE", client.t("No such service"))
1049		return false
1050	}
1051
1052	tokenString, err := sConfig.Sign(claims)
1053
1054	if err == nil {
1055		maxTokenLength := 400
1056
1057		for maxTokenLength < len(tokenString) {
1058			rb.Add(nil, server.name, "EXTJWT", msg.Params[0], serviceName, "*", tokenString[:maxTokenLength])
1059			tokenString = tokenString[maxTokenLength:]
1060		}
1061		rb.Add(nil, server.name, "EXTJWT", msg.Params[0], serviceName, tokenString)
1062	} else {
1063		rb.Add(nil, server.name, "FAIL", "EXTJWT", "UNKNOWN_ERROR", client.t("Could not generate EXTJWT token"))
1064	}
1065
1066	return false
1067}
1068
1069// HELP [<query>]
1070// HELPOP [<query>]
1071func helpHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1072	if len(msg.Params) == 0 {
1073		client.sendHelp("HELPOP", client.t(`HELPOP <argument>
1074
1075Get an explanation of <argument>, or "index" for a list of help topics.`), rb)
1076		return false
1077	}
1078
1079	argument := strings.ToLower(strings.TrimSpace(msg.Params[0]))
1080
1081	// handle index
1082	if argument == "index" {
1083		client.sendHelp("HELP", server.helpIndexManager.GetIndex(client.Languages(), client.HasMode(modes.Operator)), rb)
1084		return false
1085	}
1086
1087	helpHandler, exists := Help[argument]
1088
1089	if exists && (!helpHandler.oper || (helpHandler.oper && client.HasMode(modes.Operator))) {
1090		if helpHandler.textGenerator != nil {
1091			client.sendHelp(argument, helpHandler.textGenerator(client), rb)
1092		} else {
1093			client.sendHelp(argument, client.t(helpHandler.text), rb)
1094		}
1095	} else {
1096		rb.Add(nil, server.name, ERR_HELPNOTFOUND, client.Nick(), strings.ToUpper(utils.SafeErrorParam(argument)), client.t("Help not found"))
1097	}
1098
1099	return false
1100}
1101
1102// HISTORY <target> [<limit>]
1103// e.g., HISTORY #ubuntu 10
1104// HISTORY alice 15
1105// HISTORY #darwin 1h
1106func historyHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1107	config := server.Config()
1108	if !config.History.Enabled {
1109		rb.Notice(client.t("This command has been disabled by the server administrators"))
1110		return false
1111	}
1112
1113	items, channel, err := easySelectHistory(server, client, msg.Params)
1114
1115	if err == errNoSuchChannel {
1116		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(msg.Params[0]), client.t("No such channel"))
1117		return false
1118	} else if err != nil {
1119		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, client.t("Could not retrieve history"))
1120		return false
1121	}
1122
1123	if len(items) != 0 {
1124		if channel != nil {
1125			channel.replayHistoryItems(rb, items, true)
1126		} else {
1127			client.replayPrivmsgHistory(rb, items, "", true)
1128		}
1129	}
1130	return false
1131}
1132
1133// INFO
1134func infoHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1135	nick := client.Nick()
1136	// we do the below so that the human-readable lines in info can be translated.
1137	for _, line := range infoString1 {
1138		rb.Add(nil, server.name, RPL_INFO, nick, line)
1139	}
1140	rb.Add(nil, server.name, RPL_INFO, nick, fmt.Sprintf(client.t("This is Ergo version %s."), SemVer))
1141	if Commit != "" {
1142		rb.Add(nil, server.name, RPL_INFO, nick, fmt.Sprintf(client.t("It was built from git hash %s."), Commit))
1143	}
1144	rb.Add(nil, server.name, RPL_INFO, nick, fmt.Sprintf(client.t("It was compiled using %s."), runtime.Version()))
1145	rb.Add(nil, server.name, RPL_INFO, nick, "")
1146	rb.Add(nil, server.name, RPL_INFO, nick, client.t("Ergo is released under the MIT license."))
1147	rb.Add(nil, server.name, RPL_INFO, nick, "")
1148	rb.Add(nil, server.name, RPL_INFO, nick, client.t("Core Developers:"))
1149	for _, line := range infoString2 {
1150		rb.Add(nil, server.name, RPL_INFO, nick, line)
1151	}
1152	rb.Add(nil, server.name, RPL_INFO, nick, client.t("Former Core Developers:"))
1153	for _, line := range infoString3 {
1154		rb.Add(nil, server.name, RPL_INFO, nick, line)
1155	}
1156	rb.Add(nil, server.name, RPL_INFO, nick, client.t("For a more complete list of contributors, see our changelog:"))
1157	rb.Add(nil, server.name, RPL_INFO, nick, "    https://github.com/ergochat/ergo/blob/master/CHANGELOG.md")
1158	rb.Add(nil, server.name, RPL_INFO, nick, "")
1159	// show translators for languages other than good ole' regular English
1160	tlines := server.Languages().Translators()
1161	if 0 < len(tlines) {
1162		rb.Add(nil, server.name, RPL_INFO, nick, client.t("Translators:"))
1163		for _, line := range tlines {
1164			rb.Add(nil, server.name, RPL_INFO, nick, "    "+strings.Replace(line, "\n", ", ", -1))
1165		}
1166		rb.Add(nil, server.name, RPL_INFO, nick, "")
1167	}
1168	rb.Add(nil, server.name, RPL_ENDOFINFO, nick, client.t("End of /INFO"))
1169	return false
1170}
1171
1172// INVITE <nickname> <channel>
1173// UNINVITE <nickname> <channel>
1174func inviteHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1175	invite := msg.Command == "INVITE"
1176	nickname := msg.Params[0]
1177	channelName := msg.Params[1]
1178
1179	target := server.clients.Get(nickname)
1180	if target == nil {
1181		rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), utils.SafeErrorParam(nickname), client.t("No such nick"))
1182		return false
1183	}
1184
1185	channel := server.channels.Get(channelName)
1186	if channel == nil {
1187		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(channelName), client.t("No such channel"))
1188		return false
1189	}
1190
1191	if invite {
1192		channel.Invite(target, client, rb)
1193	} else {
1194		channel.Uninvite(target, client, rb)
1195	}
1196
1197	return false
1198}
1199
1200// ISON <nick>{ <nick>}
1201func isonHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1202	var nicks = msg.Params
1203
1204	ison := make([]string, 0, len(msg.Params))
1205	for _, nick := range nicks {
1206		currentNick := server.getCurrentNick(nick)
1207		if currentNick != "" {
1208			ison = append(ison, currentNick)
1209		}
1210	}
1211
1212	rb.Add(nil, server.name, RPL_ISON, client.nick, strings.Join(ison, " "))
1213	return false
1214}
1215
1216// JOIN <channel>{,<channel>} [<key>{,<key>}]
1217func joinHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1218	// #1417: allow `JOIN 0` with a confirmation code
1219	if msg.Params[0] == "0" {
1220		expectedCode := utils.ConfirmationCode("", rb.session.ctime)
1221		if len(msg.Params) == 1 || msg.Params[1] != expectedCode {
1222			rb.Notice(fmt.Sprintf(client.t("Warning: /JOIN 0 will remove you from all channels. To confirm, type: /JOIN 0 %s"), expectedCode))
1223		} else {
1224			for _, channel := range client.Channels() {
1225				channel.Part(client, "", rb)
1226			}
1227		}
1228		return false
1229	}
1230
1231	// handle regular JOINs
1232	channels := strings.Split(msg.Params[0], ",")
1233	var keys []string
1234	if len(msg.Params) > 1 {
1235		keys = strings.Split(msg.Params[1], ",")
1236	}
1237
1238	for i, name := range channels {
1239		if name == "" {
1240			continue // #679
1241		}
1242		var key string
1243		if len(keys) > i {
1244			key = keys[i]
1245		}
1246		err, forward := server.channels.Join(client, name, key, false, rb)
1247		if err != nil {
1248			if forward != "" {
1249				rb.Add(nil, server.name, ERR_LINKCHANNEL, client.Nick(), utils.SafeErrorParam(name), forward, client.t("Forwarding to another channel"))
1250				name = forward
1251				err, _ = server.channels.Join(client, name, key, false, rb)
1252			}
1253			if err != nil {
1254				sendJoinError(client, name, rb, err)
1255			}
1256		}
1257	}
1258	return false
1259}
1260
1261func sendJoinError(client *Client, name string, rb *ResponseBuffer, err error) {
1262	var code, errMsg, forbiddingMode string
1263	switch err {
1264	case errInsufficientPrivs:
1265		code, errMsg = ERR_NOSUCHCHANNEL, `Only server operators can create new channels`
1266	case errConfusableIdentifier:
1267		code, errMsg = ERR_NOSUCHCHANNEL, `That channel name is too close to the name of another channel`
1268	case errChannelPurged:
1269		code, errMsg = ERR_NOSUCHCHANNEL, err.Error()
1270	case errTooManyChannels:
1271		code, errMsg = ERR_TOOMANYCHANNELS, `You have joined too many channels`
1272	case errLimitExceeded:
1273		code, forbiddingMode = ERR_CHANNELISFULL, "l"
1274	case errWrongChannelKey:
1275		code, forbiddingMode = ERR_BADCHANNELKEY, "k"
1276	case errInviteOnly:
1277		code, forbiddingMode = ERR_INVITEONLYCHAN, "i"
1278	case errBanned:
1279		code, forbiddingMode = ERR_BANNEDFROMCHAN, "b"
1280	case errRegisteredOnly:
1281		code, errMsg = ERR_NEEDREGGEDNICK, `You must be registered to join that channel`
1282	default:
1283		code, errMsg = ERR_NOSUCHCHANNEL, `No such channel`
1284	}
1285	if forbiddingMode != "" {
1286		errMsg = fmt.Sprintf(client.t("Cannot join channel (+%s)"), forbiddingMode)
1287	} else {
1288		errMsg = client.t(errMsg)
1289	}
1290	rb.Add(nil, client.server.name, code, client.Nick(), utils.SafeErrorParam(name), errMsg)
1291}
1292
1293// SAJOIN [nick] #channel{,#channel}
1294func sajoinHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1295	var target *Client
1296	var channelString string
1297	if strings.HasPrefix(msg.Params[0], "#") {
1298		target = client
1299		channelString = msg.Params[0]
1300	} else {
1301		if len(msg.Params) == 1 {
1302			rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), "SAJOIN", client.t("Not enough parameters"))
1303			return false
1304		} else {
1305			target = server.clients.Get(msg.Params[0])
1306			if target == nil {
1307				rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), utils.SafeErrorParam(msg.Params[0]), "No such nick")
1308				return false
1309			}
1310			channelString = msg.Params[1]
1311		}
1312	}
1313
1314	message := fmt.Sprintf("Operator %s ran SAJOIN %s", client.Oper().Name, strings.Join(msg.Params, " "))
1315	server.snomasks.Send(sno.LocalOpers, message)
1316	server.logger.Info("opers", message)
1317
1318	channels := strings.Split(channelString, ",")
1319	for _, chname := range channels {
1320		err, _ := server.channels.Join(target, chname, "", true, rb)
1321		if err != nil {
1322			sendJoinError(client, chname, rb, err)
1323		}
1324	}
1325	return false
1326}
1327
1328// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
1329// RFC 2812 requires the number of channels to be either 1 or equal to
1330// the number of users.
1331// Addditionally, we support multiple channels and a single user.
1332func kickHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1333	hasPrivs := client.HasRoleCapabs("samode")
1334	channels := strings.Split(msg.Params[0], ",")
1335	users := strings.Split(msg.Params[1], ",")
1336	if (len(channels) != len(users)) && (len(users) != 1) && (len(channels) != 1) {
1337		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, "KICK", client.t("Not enough parameters"))
1338		return false
1339	}
1340
1341	type kickCmd struct {
1342		channel string
1343		nick    string
1344	}
1345	var kicks []kickCmd
1346	if len(users) == 1 {
1347		kicks = make([]kickCmd, 0, len(channels))
1348		// Single user, possibly multiple channels
1349		user := users[0]
1350		for _, channel := range channels {
1351			if channel == "" {
1352				continue // #679
1353			}
1354			kicks = append(kicks, kickCmd{channel, user})
1355		}
1356	} else {
1357		// Multiple users, either a single channel or as many channels
1358		// as users.
1359		kicks = make([]kickCmd, 0, len(users))
1360		channel := channels[0]
1361		for index, user := range users {
1362			if len(channels) > 1 {
1363				channel = channels[index]
1364			}
1365			if channel == "" {
1366				continue // #679
1367			}
1368			kicks = append(kicks, kickCmd{channel, user})
1369		}
1370	}
1371
1372	var comment string
1373	if len(msg.Params) > 2 {
1374		comment = msg.Params[2]
1375	}
1376	if comment == "" {
1377		comment = client.Nick()
1378	}
1379	for _, kick := range kicks {
1380		channel := server.channels.Get(kick.channel)
1381		if channel == nil {
1382			rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, utils.SafeErrorParam(kick.channel), client.t("No such channel"))
1383			continue
1384		}
1385
1386		target := server.clients.Get(kick.nick)
1387		if target == nil {
1388			rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, utils.SafeErrorParam(kick.nick), client.t("No such nick"))
1389			continue
1390		}
1391		channel.Kick(client, target, comment, rb, hasPrivs)
1392	}
1393	return false
1394}
1395
1396// KILL <nickname> <comment>
1397func killHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1398	nickname := msg.Params[0]
1399	comment := "<no reason supplied>"
1400	if len(msg.Params) > 1 {
1401		comment = msg.Params[1]
1402	}
1403
1404	target := server.clients.Get(nickname)
1405	if target == nil {
1406		rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.Nick(), utils.SafeErrorParam(nickname), client.t("No such nick"))
1407		return false
1408	} else if target.AlwaysOn() {
1409		rb.Add(nil, client.server.name, ERR_UNKNOWNERROR, client.Nick(), "KILL", fmt.Sprintf(client.t("Client %s is always-on and cannot be fully removed by /KILL; consider /NS SUSPEND instead"), target.Nick()))
1410	}
1411
1412	quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nick, comment)
1413
1414	server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s$r was killed by %s $c[grey][$r%s$c[grey]]"), target.nick, client.nick, comment))
1415
1416	target.Quit(quitMsg, nil)
1417	target.destroy(nil)
1418	return false
1419}
1420
1421// KLINE [ANDKILL] [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]]
1422// KLINE LIST
1423func klineHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1424	details := client.Details()
1425	// check oper permissions
1426	oper := client.Oper()
1427	if !oper.HasRoleCapab("ban") {
1428		rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs"))
1429		return false
1430	}
1431
1432	currentArg := 0
1433
1434	// if they say LIST, we just list the current klines
1435	if len(msg.Params) == currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "list" {
1436		bans := server.klines.AllBans()
1437
1438		if len(bans) == 0 {
1439			client.Notice("No KLINEs have been set!")
1440		}
1441
1442		for key, info := range bans {
1443			client.Notice(formatBanForListing(client, key, info))
1444		}
1445
1446		return false
1447	}
1448
1449	// when setting a ban, if they say "ANDKILL" we should also kill all users who match it
1450	var andKill bool
1451	if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "andkill" {
1452		andKill = true
1453		currentArg++
1454	}
1455
1456	// when setting a ban that covers the oper's current connection, we require them to say
1457	// "KLINE MYSELF" so that we're sure they really mean it.
1458	var klineMyself bool
1459	if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "myself" {
1460		klineMyself = true
1461		currentArg++
1462	}
1463
1464	// duration
1465	duration, err := custime.ParseDuration(msg.Params[currentArg])
1466	if err != nil {
1467		duration = 0
1468	} else {
1469		currentArg++
1470	}
1471
1472	// get mask
1473	if len(msg.Params) < currentArg+1 {
1474		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, details.nick, msg.Command, client.t("Not enough parameters"))
1475		return false
1476	}
1477	mask := msg.Params[currentArg]
1478	currentArg++
1479
1480	// check mask
1481	mask, err = CanonicalizeMaskWildcard(mask)
1482	if err != nil {
1483		rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
1484		return false
1485	}
1486
1487	matcher, err := utils.CompileGlob(mask, false)
1488	if err != nil {
1489		rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
1490		return false
1491	}
1492
1493	for _, clientMask := range client.AllNickmasks() {
1494		if !klineMyself && matcher.MatchString(clientMask) {
1495			rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("This ban matches you. To KLINE yourself, you must use the command:  /KLINE MYSELF <arguments>"))
1496			return false
1497		}
1498	}
1499
1500	// check remote
1501	if len(msg.Params) > currentArg && msg.Params[currentArg] == "ON" {
1502		rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Remote servers not yet supported"))
1503		return false
1504	}
1505
1506	// get oper name
1507	operName := oper.Name
1508	if operName == "" {
1509		operName = server.name
1510	}
1511
1512	// get comment(s)
1513	reason, operReason := getReasonsFromParams(msg.Params, currentArg)
1514
1515	err = server.klines.AddMask(mask, duration, reason, operReason, operName)
1516	if err != nil {
1517		rb.Notice(fmt.Sprintf(client.t("Could not successfully save new K-LINE: %s"), err.Error()))
1518		return false
1519	}
1520
1521	var snoDescription string
1522	if duration != 0 {
1523		rb.Notice(fmt.Sprintf(client.t("Added temporary (%[1]s) K-Line for %[2]s"), duration.String(), mask))
1524		snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added temporary (%s) K-Line for %s"), details.nick, operName, duration.String(), mask)
1525	} else {
1526		rb.Notice(fmt.Sprintf(client.t("Added K-Line for %s"), mask))
1527		snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added K-Line for %s"), details.nick, operName, mask)
1528	}
1529	server.snomasks.Send(sno.LocalXline, snoDescription)
1530
1531	var killClient bool
1532	if andKill {
1533		var clientsToKill []*Client
1534		var killedClientNicks []string
1535
1536		for _, mcl := range server.clients.AllClients() {
1537			for _, clientMask := range mcl.AllNickmasks() {
1538				if matcher.MatchString(clientMask) {
1539					clientsToKill = append(clientsToKill, mcl)
1540					killedClientNicks = append(killedClientNicks, mcl.nick)
1541					break
1542				}
1543			}
1544		}
1545
1546		for _, mcl := range clientsToKill {
1547			mcl.Quit(fmt.Sprintf(mcl.t("You have been banned from this server (%s)"), reason), nil)
1548			if mcl == client {
1549				killClient = true
1550			} else {
1551				// if mcl == client, we kill them below
1552				mcl.destroy(nil)
1553			}
1554		}
1555
1556		// send snomask
1557		sort.Strings(killedClientNicks)
1558		server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s [%s] killed %d clients with a KLINE $c[grey][$r%s$c[grey]]"), details.nick, operName, len(killedClientNicks), strings.Join(killedClientNicks, ", ")))
1559	}
1560
1561	return killClient
1562}
1563
1564// LANGUAGE <code>{ <code>}
1565func languageHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1566	nick := client.Nick()
1567	alreadyDoneLanguages := make(map[string]bool)
1568	var appliedLanguages []string
1569
1570	lm := server.Languages()
1571	supportedLanguagesCount := lm.Count()
1572	if supportedLanguagesCount < len(msg.Params) {
1573		rb.Add(nil, client.server.name, ERR_TOOMANYLANGUAGES, nick, strconv.Itoa(supportedLanguagesCount), client.t("You specified too many languages"))
1574		return false
1575	}
1576
1577	for _, value := range msg.Params {
1578		value = strings.ToLower(value)
1579		// strip ~ from the language if it has it
1580		value = strings.TrimPrefix(value, "~")
1581
1582		// silently ignore empty languages or those with spaces in them
1583		if len(value) == 0 || strings.Contains(value, " ") {
1584			continue
1585		}
1586
1587		_, exists := lm.Languages[value]
1588		if !exists {
1589			rb.Add(nil, client.server.name, ERR_NOLANGUAGE, nick, fmt.Sprintf(client.t("Language %s is not supported by this server"), value))
1590			return false
1591		}
1592
1593		// if we've already applied the given language, skip it
1594		_, exists = alreadyDoneLanguages[value]
1595		if exists {
1596			continue
1597		}
1598
1599		appliedLanguages = append(appliedLanguages, value)
1600	}
1601
1602	var langsToSet []string
1603	if !(len(appliedLanguages) == 1 && appliedLanguages[0] == "en") {
1604		langsToSet = appliedLanguages
1605	}
1606	client.SetLanguages(langsToSet)
1607
1608	params := make([]string, len(appliedLanguages)+2)
1609	params[0] = nick
1610	copy(params[1:], appliedLanguages)
1611	params[len(params)-1] = client.t("Language preferences have been set")
1612
1613	rb.Add(nil, client.server.name, RPL_YOURLANGUAGESARE, params...)
1614
1615	return false
1616}
1617
1618// LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
1619func listHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1620	config := server.Config()
1621	if time.Since(client.ctime) < config.Channels.ListDelay && client.Account() == "" && !client.HasMode(modes.Operator) {
1622		remaining := time.Until(client.ctime.Add(config.Channels.ListDelay))
1623		rb.Notice(fmt.Sprintf(client.t("This server requires that you wait %v after connecting before you can use /LIST. You have %v left."), config.Channels.ListDelay, remaining))
1624		rb.Add(nil, server.name, RPL_LISTEND, client.Nick(), client.t("End of LIST"))
1625		return false
1626	}
1627
1628	// get channels
1629	var channels []string
1630	for _, param := range msg.Params {
1631		if 0 < len(param) && param[0] == '#' {
1632			for _, channame := range strings.Split(param, ",") {
1633				if 0 < len(channame) && channame[0] == '#' {
1634					channels = append(channels, channame)
1635				}
1636			}
1637		}
1638	}
1639
1640	// get elist conditions
1641	var matcher elistMatcher
1642	for _, param := range msg.Params {
1643		if len(param) < 1 {
1644			continue
1645		}
1646
1647		if param[0] == '<' {
1648			param = param[1:]
1649			val, err := strconv.Atoi(param)
1650			if err != nil {
1651				continue
1652			}
1653			matcher.MaxClientsActive = true
1654			matcher.MaxClients = val - 1 // -1 because < means less than the given number
1655		}
1656		if param[0] == '>' {
1657			param = param[1:]
1658			val, err := strconv.Atoi(param)
1659			if err != nil {
1660				continue
1661			}
1662			matcher.MinClientsActive = true
1663			matcher.MinClients = val + 1 // +1 because > means more than the given number
1664		}
1665	}
1666
1667	nick := client.Nick()
1668	rplList := func(channel *Channel) {
1669		members, name, topic := channel.listData()
1670		rb.Add(nil, client.server.name, RPL_LIST, nick, name, strconv.Itoa(members), topic)
1671	}
1672
1673	clientIsOp := client.HasRoleCapabs("sajoin")
1674	if len(channels) == 0 {
1675		for _, channel := range server.channels.Channels() {
1676			if !clientIsOp && channel.flags.HasMode(modes.Secret) {
1677				continue
1678			}
1679			if matcher.Matches(channel) {
1680				rplList(channel)
1681			}
1682		}
1683	} else {
1684		// limit regular users to only listing one channel
1685		if !clientIsOp {
1686			channels = channels[:1]
1687		}
1688
1689		for _, chname := range channels {
1690			channel := server.channels.Get(chname)
1691			if channel == nil || (!clientIsOp && channel.flags.HasMode(modes.Secret)) {
1692				if len(chname) > 0 {
1693					rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, utils.SafeErrorParam(chname), client.t("No such channel"))
1694				}
1695				continue
1696			}
1697			if matcher.Matches(channel) {
1698				rplList(channel)
1699			}
1700		}
1701	}
1702	rb.Add(nil, server.name, RPL_LISTEND, client.nick, client.t("End of LIST"))
1703	return false
1704}
1705
1706// LUSERS [<mask> [<server>]]
1707func lusersHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1708	server.Lusers(client, rb)
1709	return false
1710}
1711
1712// MODE <target> [<modestring> [<mode arguments>...]]
1713func modeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1714	if 0 < len(msg.Params[0]) && msg.Params[0][0] == '#' {
1715		return cmodeHandler(server, client, msg, rb)
1716	}
1717	return umodeHandler(server, client, msg, rb)
1718}
1719
1720// MODE <channel> [<modestring> [<mode arguments>...]]
1721func cmodeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1722	channel := server.channels.Get(msg.Params[0])
1723
1724	if channel == nil {
1725		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, utils.SafeErrorParam(msg.Params[0]), client.t("No such channel"))
1726		return false
1727	}
1728
1729	var changes modes.ModeChanges
1730	if 1 < len(msg.Params) {
1731		// parse out real mode changes
1732		params := msg.Params[1:]
1733		var unknown map[rune]bool
1734		changes, unknown = modes.ParseChannelModeChanges(params...)
1735
1736		// alert for unknown mode changes
1737		for char := range unknown {
1738			rb.Add(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), client.t("is an unknown mode character to me"))
1739		}
1740		if len(unknown) == 1 && len(changes) == 0 {
1741			return false
1742		}
1743	}
1744
1745	isSamode := msg.Command == "SAMODE"
1746	if isSamode {
1747		message := fmt.Sprintf("Operator %s ran SAMODE %s", client.Oper().Name, strings.Join(msg.Params, " "))
1748		server.snomasks.Send(sno.LocalOpers, message)
1749		server.logger.Info("opers", message)
1750	}
1751
1752	// process mode changes, include list operations (an empty set of changes does a list)
1753	applied := channel.ApplyChannelModeChanges(client, isSamode, changes, rb)
1754	details := client.Details()
1755	isBot := client.HasMode(modes.Bot)
1756	announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)
1757
1758	return false
1759}
1760
1761func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName, account string, isBot bool, rb *ResponseBuffer) {
1762	// send out changes
1763	if len(applied) > 0 {
1764		message := utils.MakeMessage("")
1765		changeStrings := applied.Strings()
1766		for _, changeString := range changeStrings {
1767			message.Split = append(message.Split, utils.MessagePair{Message: changeString})
1768		}
1769		args := append([]string{channel.name}, changeStrings...)
1770		rb.AddFromClient(message.Time, message.Msgid, source, accountName, isBot, nil, "MODE", args...)
1771		for _, member := range channel.Members() {
1772			for _, session := range member.Sessions() {
1773				if session != rb.session {
1774					session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, isBot, nil, "MODE", args...)
1775				}
1776			}
1777		}
1778		channel.AddHistoryItem(history.Item{
1779			Type:        history.Mode,
1780			Nick:        source,
1781			AccountName: accountName,
1782			Message:     message,
1783			IsBot:       isBot,
1784		}, account)
1785	}
1786}
1787
1788// MODE <client> [<modestring> [<mode arguments>...]]
1789func umodeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1790	cDetails := client.Details()
1791	target := server.clients.Get(msg.Params[0])
1792	if target == nil {
1793		rb.Add(nil, server.name, ERR_NOSUCHNICK, cDetails.nick, utils.SafeErrorParam(msg.Params[0]), client.t("No such nick"))
1794		return false
1795	}
1796
1797	targetNick := target.Nick()
1798	hasPrivs := client == target || msg.Command == "SAMODE"
1799
1800	if !hasPrivs {
1801		if len(msg.Params) > 1 {
1802			rb.Add(nil, server.name, ERR_USERSDONTMATCH, cDetails.nick, client.t("Can't change modes for other users"))
1803		} else {
1804			rb.Add(nil, server.name, ERR_USERSDONTMATCH, cDetails.nick, client.t("Can't view modes for other users"))
1805		}
1806		return false
1807	}
1808
1809	if msg.Command == "SAMODE" {
1810		message := fmt.Sprintf("Operator %s ran SAMODE %s", client.Oper().Name, strings.Join(msg.Params, " "))
1811		server.snomasks.Send(sno.LocalOpers, message)
1812		server.logger.Info("opers", message)
1813	}
1814
1815	// applied mode changes
1816	applied := make(modes.ModeChanges, 0)
1817
1818	if 1 < len(msg.Params) {
1819		// parse out real mode changes
1820		params := msg.Params[1:]
1821		changes, unknown := modes.ParseUserModeChanges(params...)
1822
1823		// alert for unknown mode changes
1824		for char := range unknown {
1825			rb.Add(nil, server.name, ERR_UNKNOWNMODE, cDetails.nick, string(char), client.t("is an unknown mode character to me"))
1826		}
1827		if len(unknown) == 1 && len(changes) == 0 {
1828			return false
1829		}
1830
1831		// apply mode changes
1832		applied = ApplyUserModeChanges(target, changes, msg.Command == "SAMODE", nil)
1833	}
1834
1835	if len(applied) > 0 {
1836		args := append([]string{targetNick}, applied.Strings()...)
1837		rb.Add(nil, cDetails.nickMask, "MODE", args...)
1838	} else if hasPrivs {
1839		rb.Add(nil, server.name, RPL_UMODEIS, targetNick, target.ModeString())
1840		if target.HasMode(modes.Operator) {
1841			masks := server.snomasks.String(target)
1842			if 0 < len(masks) {
1843				rb.Add(nil, server.name, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
1844			}
1845		}
1846	}
1847	return false
1848}
1849
1850// get the correct capitalization of a nick (if it's online), otherwise return ""
1851func (server *Server) getCurrentNick(nick string) (result string) {
1852	if service, isService := OragonoServices[strings.ToLower(nick)]; isService {
1853		return service.Name
1854	} else if iclient := server.clients.Get(nick); iclient != nil {
1855		return iclient.Nick()
1856	}
1857	return ""
1858}
1859
1860// MONITOR <subcmd> [params...]
1861func monitorHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1862	handler, exists := monitorSubcommands[strings.ToLower(msg.Params[0])]
1863
1864	if !exists {
1865		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "MONITOR", msg.Params[0], client.t("Unknown subcommand"))
1866		return false
1867	}
1868
1869	return handler(server, client, msg, rb)
1870}
1871
1872// MONITOR - <target>{,<target>}
1873func monitorRemoveHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1874	if len(msg.Params) < 2 {
1875		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
1876		return false
1877	}
1878
1879	targets := strings.Split(msg.Params[1], ",")
1880	for _, target := range targets {
1881		server.monitorManager.Remove(rb.session, target)
1882	}
1883
1884	return false
1885}
1886
1887// MONITOR + <target>{,<target>}
1888func monitorAddHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1889	if len(msg.Params) < 2 {
1890		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
1891		return false
1892	}
1893
1894	var online []string
1895	var offline []string
1896
1897	limits := server.Config().Limits
1898
1899	targets := strings.Split(msg.Params[1], ",")
1900	for _, target := range targets {
1901		// check name length
1902		if len(target) < 1 || len(targets) > limits.NickLen {
1903			continue
1904		}
1905
1906		// add target
1907		err := server.monitorManager.Add(rb.session, target, limits.MonitorEntries)
1908		if err == errMonitorLimitExceeded {
1909			rb.Add(nil, server.name, ERR_MONLISTFULL, client.Nick(), strconv.Itoa(limits.MonitorEntries), strings.Join(targets, ","))
1910			break
1911		} else if err != nil {
1912			continue
1913		}
1914
1915		currentNick := server.getCurrentNick(target)
1916		// add to online / offline lists
1917		if currentNick != "" {
1918			online = append(online, currentNick)
1919		} else {
1920			offline = append(offline, target)
1921		}
1922	}
1923
1924	if len(online) > 0 {
1925		rb.Add(nil, server.name, RPL_MONONLINE, client.Nick(), strings.Join(online, ","))
1926	}
1927	if len(offline) > 0 {
1928		rb.Add(nil, server.name, RPL_MONOFFLINE, client.Nick(), strings.Join(offline, ","))
1929	}
1930
1931	return false
1932}
1933
1934// MONITOR C
1935func monitorClearHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1936	server.monitorManager.RemoveAll(rb.session)
1937	return false
1938}
1939
1940// MONITOR L
1941func monitorListHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1942	nick := client.Nick()
1943	monitorList := server.monitorManager.List(rb.session)
1944
1945	var nickList []string
1946	for _, cfnick := range monitorList {
1947		replynick := cfnick
1948		currentNick := server.getCurrentNick(cfnick)
1949		// report the uncasefolded nick if it's available, i.e., the client is online
1950		if currentNick != "" {
1951			replynick = currentNick
1952		}
1953		nickList = append(nickList, replynick)
1954	}
1955
1956	for _, line := range utils.BuildTokenLines(maxLastArgLength, nickList, ",") {
1957		rb.Add(nil, server.name, RPL_MONLIST, nick, line)
1958	}
1959
1960	rb.Add(nil, server.name, RPL_ENDOFMONLIST, nick, "End of MONITOR list")
1961
1962	return false
1963}
1964
1965// MONITOR S
1966func monitorStatusHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1967	var online []string
1968	var offline []string
1969
1970	monitorList := server.monitorManager.List(rb.session)
1971
1972	for _, name := range monitorList {
1973		currentNick := server.getCurrentNick(name)
1974		if currentNick != "" {
1975			online = append(online, currentNick)
1976		} else {
1977			offline = append(offline, name)
1978		}
1979	}
1980
1981	if len(online) > 0 {
1982		for _, line := range utils.BuildTokenLines(maxLastArgLength, online, ",") {
1983			rb.Add(nil, server.name, RPL_MONONLINE, client.Nick(), line)
1984		}
1985	}
1986	if len(offline) > 0 {
1987		for _, line := range utils.BuildTokenLines(maxLastArgLength, offline, ",") {
1988			rb.Add(nil, server.name, RPL_MONOFFLINE, client.Nick(), line)
1989		}
1990	}
1991
1992	return false
1993}
1994
1995// MOTD
1996func motdHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
1997	server.MOTD(client, rb)
1998	return false
1999}
2000
2001// NAMES [<channel>{,<channel>} [target]]
2002func namesHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2003	var channels []string
2004	if len(msg.Params) > 0 {
2005		channels = strings.Split(msg.Params[0], ",")
2006	}
2007
2008	// TODO: in a post-federation world, process `target` (server to forward request to)
2009
2010	// implement the modern behavior: https://modern.ircdocs.horse/#names-message
2011	// "Servers MAY only return information about the first <channel> and silently ignore the others."
2012	// "If no parameter is given for this command, servers SHOULD return one RPL_ENDOFNAMES numeric
2013	//  with the <channel> parameter set to an asterix character"
2014
2015	if len(channels) == 0 {
2016		rb.Add(nil, server.name, RPL_ENDOFNAMES, client.Nick(), "*", client.t("End of NAMES list"))
2017		return false
2018	}
2019
2020	chname := channels[0]
2021	success := false
2022	channel := server.channels.Get(chname)
2023	if channel != nil {
2024		if !channel.flags.HasMode(modes.Secret) || channel.hasClient(client) || client.HasRoleCapabs("sajoin") {
2025			channel.Names(client, rb)
2026			success = true
2027		}
2028	}
2029	if !success { // channel.Names() sends this numeric itself on success
2030		rb.Add(nil, server.name, RPL_ENDOFNAMES, client.Nick(), chname, client.t("End of NAMES list"))
2031	}
2032	return false
2033}
2034
2035// NICK <nickname>
2036func nickHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2037	if client.registered {
2038		if client.account == "" && server.Config().Accounts.NickReservation.ForbidAnonNickChanges {
2039			rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), client.t("You may not change your nickname"))
2040			return false
2041		}
2042		performNickChange(server, client, client, nil, msg.Params[0], rb)
2043	} else {
2044		client.preregNick = msg.Params[0]
2045	}
2046	return false
2047}
2048
2049// check whether a PRIVMSG or NOTICE is too long to be relayed without truncation
2050func validateLineLen(msgType history.ItemType, source, target, payload string) (ok bool) {
2051	// :source PRIVMSG #target :payload\r\n
2052	// 1: initial colon on prefix
2053	// 1: space between prefix and command
2054	// 1: space between command and target (first parameter)
2055	// 1: space between target and payload (second parameter)
2056	// 1: colon to send the payload as a trailing (we force trailing for PRIVMSG and NOTICE)
2057	// 2: final \r\n
2058	limit := MaxLineLen - 7
2059	limit -= len(source)
2060	switch msgType {
2061	case history.Privmsg:
2062		limit -= 7
2063	case history.Notice:
2064		limit -= 6
2065	default:
2066		return true
2067	}
2068	limit -= len(payload)
2069	return limit >= 0
2070}
2071
2072// check validateLineLen for an entire SplitMessage (which may consist of multiple lines)
2073func validateSplitMessageLen(msgType history.ItemType, source, target string, message utils.SplitMessage) (ok bool) {
2074	if message.Is512() {
2075		return validateLineLen(msgType, source, target, message.Message)
2076	} else {
2077		for _, messagePair := range message.Split {
2078			if !validateLineLen(msgType, source, target, messagePair.Message) {
2079				return false
2080			}
2081		}
2082		return true
2083	}
2084}
2085
2086// helper to store a batched PRIVMSG in the session object
2087func absorbBatchedMessage(server *Server, client *Client, msg ircmsg.Message, batchTag string, histType history.ItemType, rb *ResponseBuffer) {
2088	var errorCode, errorMessage string
2089	defer func() {
2090		if errorCode != "" {
2091			if histType != history.Notice {
2092				rb.Add(nil, server.name, "FAIL", "BATCH", errorCode, errorMessage)
2093			}
2094			rb.session.EndMultilineBatch("")
2095		}
2096	}()
2097
2098	if batchTag != rb.session.batch.label {
2099		errorCode, errorMessage = "MULTILINE_INVALID", client.t("Incorrect batch tag sent")
2100		return
2101	} else if len(msg.Params) < 2 {
2102		errorCode, errorMessage = "MULTILINE_INVALID", client.t("Invalid multiline batch")
2103		return
2104	}
2105	rb.session.batch.command = msg.Command
2106	isConcat, _ := msg.GetTag(caps.MultilineConcatTag)
2107	if isConcat && len(msg.Params[1]) == 0 {
2108		errorCode, errorMessage = "MULTILINE_INVALID", client.t("Cannot send a blank line with the multiline concat tag")
2109		return
2110	}
2111	if !isConcat && len(rb.session.batch.message.Split) != 0 {
2112		rb.session.batch.lenBytes++ // bill for the newline
2113	}
2114	rb.session.batch.message.Append(msg.Params[1], isConcat)
2115	rb.session.batch.lenBytes += len(msg.Params[1])
2116	config := server.Config()
2117	if config.Limits.Multiline.MaxBytes < rb.session.batch.lenBytes {
2118		errorCode, errorMessage = "MULTILINE_MAX_BYTES", strconv.Itoa(config.Limits.Multiline.MaxBytes)
2119	} else if config.Limits.Multiline.MaxLines != 0 && config.Limits.Multiline.MaxLines < rb.session.batch.message.LenLines() {
2120		errorCode, errorMessage = "MULTILINE_MAX_LINES", strconv.Itoa(config.Limits.Multiline.MaxLines)
2121	}
2122}
2123
2124// NOTICE <target>{,<target>} <message>
2125// PRIVMSG <target>{,<target>} <message>
2126// TAGMSG <target>{,<target>}
2127func messageHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2128	histType, err := msgCommandToHistType(msg.Command)
2129	if err != nil {
2130		return false
2131	}
2132
2133	if isBatched, batchTag := msg.GetTag("batch"); isBatched {
2134		absorbBatchedMessage(server, client, msg, batchTag, histType, rb)
2135		return false
2136	}
2137
2138	clientOnlyTags := msg.ClientOnlyTags()
2139	if histType == history.Tagmsg && len(clientOnlyTags) == 0 {
2140		// nothing to do
2141		return false
2142	}
2143
2144	targets := strings.Split(msg.Params[0], ",")
2145	var message string
2146	if len(msg.Params) > 1 {
2147		message = msg.Params[1]
2148	}
2149	if histType != history.Tagmsg && message == "" {
2150		rb.Add(nil, server.name, ERR_NOTEXTTOSEND, client.Nick(), client.t("No text to send"))
2151		return false
2152	}
2153
2154	isCTCP := utils.IsRestrictedCTCPMessage(message)
2155	if histType == history.Privmsg && !isCTCP {
2156		client.UpdateActive(rb.session)
2157	}
2158
2159	if rb.session.isTor && isCTCP {
2160		// note that error replies are never sent for NOTICE
2161		if histType != history.Notice {
2162			rb.Notice(client.t("CTCP messages are disabled over Tor"))
2163		}
2164		return false
2165	}
2166
2167	for i, targetString := range targets {
2168		// max of four targets per privmsg
2169		if i == maxTargets {
2170			break
2171		}
2172
2173		config := server.Config()
2174		if config.isRelaymsgIdentifier(targetString) {
2175			if histType == history.Privmsg {
2176				rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), targetString, client.t("Relayed users cannot receive private messages"))
2177			}
2178			// TAGMSG/NOTICEs are intentionally silently dropped
2179			continue
2180		}
2181
2182		// each target gets distinct msgids
2183		splitMsg := utils.MakeMessage(message)
2184		dispatchMessageToTarget(client, clientOnlyTags, histType, msg.Command, targetString, splitMsg, rb)
2185	}
2186	return false
2187}
2188
2189func dispatchMessageToTarget(client *Client, tags map[string]string, histType history.ItemType, command, target string, message utils.SplitMessage, rb *ResponseBuffer) {
2190	server := client.server
2191
2192	prefixes, target := modes.SplitChannelMembershipPrefixes(target)
2193	lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
2194
2195	if len(target) == 0 {
2196		return
2197	} else if target[0] == '#' {
2198		channel := server.channels.Get(target)
2199		if channel == nil {
2200			if histType != history.Notice {
2201				rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(target), client.t("No such channel"))
2202			}
2203			return
2204		}
2205		channel.SendSplitMessage(command, lowestPrefix, tags, client, message, rb)
2206	} else if target[0] == '$' && len(target) > 2 && client.Oper().HasRoleCapab("massmessage") {
2207		details := client.Details()
2208		matcher, err := utils.CompileGlob(target[2:], false)
2209		if err != nil {
2210			rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, command, client.t("Erroneous target"))
2211			return
2212		}
2213
2214		nickMaskString := details.nickMask
2215		accountName := details.accountName
2216		isBot := client.HasMode(modes.Bot)
2217		for _, tClient := range server.clients.AllClients() {
2218			if (target[1] == '$' && matcher.MatchString(tClient.server.name)) || // $$servername
2219				(target[1] == '#' && matcher.MatchString(tClient.Hostname())) { // $#hostname
2220
2221				tnick := tClient.Nick()
2222				for _, session := range tClient.Sessions() {
2223					session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, isBot, nil, command, tnick, message)
2224				}
2225			}
2226		}
2227	} else {
2228		lowercaseTarget := strings.ToLower(target)
2229		service, isService := OragonoServices[lowercaseTarget]
2230		_, isZNC := zncHandlers[lowercaseTarget]
2231
2232		if isService || isZNC {
2233			details := client.Details()
2234			rb.addEchoMessage(tags, details.nickMask, details.accountName, command, target, message)
2235			if histType != history.Privmsg {
2236				return // NOTICE and TAGMSG to services are ignored
2237			}
2238			if isService {
2239				servicePrivmsgHandler(service, server, client, message.Message, rb)
2240			} else if isZNC {
2241				zncPrivmsgHandler(client, lowercaseTarget, message.Message, rb)
2242			}
2243			return
2244		}
2245
2246		user := server.clients.Get(target)
2247		if user == nil {
2248			if histType != history.Notice {
2249				rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), target, "No such nick")
2250			}
2251			return
2252		}
2253
2254		// Restrict CTCP message for target user with +T
2255		if user.modes.HasMode(modes.UserNoCTCP) && message.IsRestrictedCTCPMessage() {
2256			return
2257		}
2258
2259		tDetails := user.Details()
2260		tnick := tDetails.nick
2261
2262		details := client.Details()
2263		if details.account == "" && server.Defcon() <= 3 {
2264			rb.Add(nil, server.name, ERR_NEEDREGGEDNICK, client.Nick(), tnick, client.t("Direct messages from unregistered users are temporarily restricted"))
2265			return
2266		}
2267		// restrict messages appropriately when +R is set
2268		if details.account == "" && user.HasMode(modes.RegisteredOnly) {
2269			rb.Add(nil, server.name, ERR_NEEDREGGEDNICK, client.Nick(), tnick, client.t("You must be registered to send a direct message to this user"))
2270			return
2271		}
2272		if !client.server.Config().Server.Compatibility.allowTruncation {
2273			if !validateSplitMessageLen(histType, client.NickMaskString(), tnick, message) {
2274				rb.Add(nil, server.name, ERR_INPUTTOOLONG, client.Nick(), client.t("Line too long to be relayed without truncation"))
2275				return
2276			}
2277		}
2278		nickMaskString := details.nickMask
2279		accountName := details.accountName
2280		var deliverySessions []*Session
2281		deliverySessions = append(deliverySessions, user.Sessions()...)
2282		// all sessions of the sender, except the originating session, get a copy as well:
2283		if client != user {
2284			for _, session := range client.Sessions() {
2285				if session != rb.session {
2286					deliverySessions = append(deliverySessions, session)
2287				}
2288			}
2289		}
2290
2291		isBot := client.HasMode(modes.Bot)
2292		for _, session := range deliverySessions {
2293			hasTagsCap := session.capabilities.Has(caps.MessageTags)
2294			// don't send TAGMSG at all if they don't have the tags cap
2295			if histType == history.Tagmsg && hasTagsCap {
2296				session.sendFromClientInternal(false, message.Time, message.Msgid, nickMaskString, accountName, isBot, tags, command, tnick)
2297			} else if histType != history.Tagmsg && !(session.isTor && message.IsRestrictedCTCPMessage()) {
2298				tagsToSend := tags
2299				if !hasTagsCap {
2300					tagsToSend = nil
2301				}
2302				session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, isBot, tagsToSend, command, tnick, message)
2303			}
2304		}
2305
2306		// the originating session may get an echo message:
2307		rb.addEchoMessage(tags, nickMaskString, accountName, command, tnick, message)
2308		if histType != history.Notice {
2309			//TODO(dan): possibly implement cooldown of away notifications to users
2310			if away, awayMessage := user.Away(); away {
2311				rb.Add(nil, server.name, RPL_AWAY, client.Nick(), tnick, awayMessage)
2312			}
2313		}
2314
2315		config := server.Config()
2316		if !config.History.Enabled {
2317			return
2318		}
2319		item := history.Item{
2320			Type:    histType,
2321			Message: message,
2322			Tags:    tags,
2323		}
2324		client.addHistoryItem(user, item, &details, &tDetails, config)
2325	}
2326}
2327
2328func itemIsStorable(item *history.Item, config *Config) bool {
2329	switch item.Type {
2330	case history.Tagmsg:
2331		if config.History.TagmsgStorage.Default {
2332			for _, blacklistedTag := range config.History.TagmsgStorage.Blacklist {
2333				if _, ok := item.Tags[blacklistedTag]; ok {
2334					return false
2335				}
2336			}
2337			return true
2338		} else {
2339			for _, whitelistedTag := range config.History.TagmsgStorage.Whitelist {
2340				if _, ok := item.Tags[whitelistedTag]; ok {
2341					return true
2342				}
2343			}
2344			return false
2345		}
2346	case history.Privmsg, history.Notice:
2347		// don't store CTCP other than ACTION
2348		return !item.Message.IsRestrictedCTCPMessage()
2349	default:
2350		return true
2351	}
2352}
2353
2354// NPC <target> <sourcenick> <message>
2355func npcHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2356	target := msg.Params[0]
2357	fakeSource := msg.Params[1]
2358	message := msg.Params[2:]
2359
2360	sendRoleplayMessage(server, client, fakeSource, target, false, false, message, rb)
2361
2362	return false
2363}
2364
2365// NPCA <target> <sourcenick> <message>
2366func npcaHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2367	target := msg.Params[0]
2368	fakeSource := msg.Params[1]
2369	message := msg.Params[2:]
2370
2371	sendRoleplayMessage(server, client, fakeSource, target, false, true, message, rb)
2372
2373	return false
2374}
2375
2376// OPER <name> [password]
2377func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2378	if client.HasMode(modes.Operator) {
2379		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "OPER", client.t("You're already opered-up!"))
2380		return false
2381	}
2382
2383	// must pass at least one check, and all enabled checks
2384	var checkPassed, checkFailed, passwordFailed bool
2385	oper := server.GetOperator(msg.Params[0])
2386	if oper != nil {
2387		if oper.Certfp != "" {
2388			if oper.Certfp == rb.session.certfp {
2389				checkPassed = true
2390			} else {
2391				checkFailed = true
2392			}
2393		}
2394		if !checkFailed && oper.Pass != nil {
2395			if len(msg.Params) == 1 {
2396				checkFailed = true
2397			} else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil {
2398				checkFailed = true
2399				passwordFailed = true
2400			} else {
2401				checkPassed = true
2402			}
2403		}
2404	}
2405
2406	if !checkPassed || checkFailed {
2407		rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect"))
2408		// #951: only disconnect them if we actually tried to check a password for them
2409		if passwordFailed {
2410			client.Quit(client.t("Password incorrect"), rb.session)
2411			return true
2412		} else {
2413			return false
2414		}
2415	}
2416
2417	if oper != nil {
2418		applyOper(client, oper, rb)
2419	}
2420	return false
2421}
2422
2423// adds or removes operator status
2424// XXX: to add oper, this calls into ApplyUserModeChanges, but to remove oper,
2425// ApplyUserModeChanges calls into this, because the commands are asymmetric
2426// (/OPER to add, /MODE self -o to remove)
2427func applyOper(client *Client, oper *Oper, rb *ResponseBuffer) {
2428	details := client.Details()
2429	client.SetOper(oper)
2430	newDetails := client.Details()
2431	if details.nickMask != newDetails.nickMask {
2432		client.sendChghost(details.nickMask, newDetails.hostname)
2433	}
2434
2435	if oper != nil {
2436		// set new modes: modes.Operator, plus anything specified in the config
2437		modeChanges := make([]modes.ModeChange, len(oper.Modes)+1)
2438		modeChanges[0] = modes.ModeChange{
2439			Mode: modes.Operator,
2440			Op:   modes.Add,
2441		}
2442		copy(modeChanges[1:], oper.Modes)
2443		applied := ApplyUserModeChanges(client, modeChanges, true, oper)
2444
2445		client.server.logger.Info("opers", details.nick, "opered up as", oper.Name)
2446		client.server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]"), newDetails.nickMask, oper.Name))
2447
2448		rb.Broadcast(nil, client.server.name, RPL_YOUREOPER, details.nick, client.t("You are now an IRC operator"))
2449		args := append([]string{details.nick}, applied.Strings()...)
2450		rb.Broadcast(nil, client.server.name, "MODE", args...)
2451	} else {
2452		client.server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client deopered $c[grey][$r%s$c[grey]]"), newDetails.nickMask))
2453	}
2454
2455	for _, session := range client.Sessions() {
2456		// client may now be unthrottled by the fakelag system
2457		session.resetFakelag()
2458	}
2459}
2460
2461// DEOPER
2462func deoperHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2463	if client.Oper() == nil {
2464		rb.Notice(client.t("Insufficient oper privs"))
2465		return false
2466	}
2467	// pretend they sent /MODE $nick -o
2468	fakeModeMsg := ircmsg.MakeMessage(nil, "", "MODE", client.Nick(), "-o")
2469	return umodeHandler(server, client, fakeModeMsg, rb)
2470}
2471
2472// PART <channel>{,<channel>} [<reason>]
2473func partHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2474	channels := strings.Split(msg.Params[0], ",")
2475	var reason string
2476	if len(msg.Params) > 1 {
2477		reason = msg.Params[1]
2478	}
2479
2480	for _, chname := range channels {
2481		if chname == "" {
2482			continue // #679
2483		}
2484		err := server.channels.Part(client, chname, reason, rb)
2485		if err == errNoSuchChannel {
2486			rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, utils.SafeErrorParam(chname), client.t("No such channel"))
2487		}
2488	}
2489	return false
2490}
2491
2492// PASS <password>
2493func passHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2494	if client.registered {
2495		rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister"))
2496		return false
2497	}
2498	// only give them one try to run the PASS command (if a server password is set,
2499	// then all code paths end with this variable being set):
2500	if rb.session.passStatus != serverPassUnsent {
2501		return false
2502	}
2503
2504	password := msg.Params[0]
2505	config := server.Config()
2506
2507	if config.Accounts.LoginViaPassCommand {
2508		colonIndex := strings.IndexByte(password, ':')
2509		if colonIndex != -1 && client.Account() == "" {
2510			account, accountPass := password[:colonIndex], password[colonIndex+1:]
2511			if strudelIndex := strings.IndexByte(account, '@'); strudelIndex != -1 {
2512				account, rb.session.deviceID = account[:strudelIndex], account[strudelIndex+1:]
2513			}
2514			err := server.accounts.AuthenticateByPassphrase(client, account, accountPass)
2515			if err == nil {
2516				sendSuccessfulAccountAuth(nil, client, rb, true)
2517				// login-via-pass-command entails that we do not need to check
2518				// an actual server password (either no password or skip-server-password)
2519				rb.session.passStatus = serverPassSuccessful
2520				return false
2521			}
2522		}
2523	}
2524	// if login-via-PASS failed for any reason, proceed to try and interpret the
2525	// provided password as the server password
2526
2527	serverPassword := config.Server.passwordBytes
2528
2529	// if no password exists, skip checking
2530	if serverPassword == nil {
2531		return false
2532	}
2533
2534	// check the provided password
2535	if bcrypt.CompareHashAndPassword(serverPassword, []byte(password)) == nil {
2536		rb.session.passStatus = serverPassSuccessful
2537	} else {
2538		rb.session.passStatus = serverPassFailed
2539	}
2540
2541	// if they failed the check, we'll bounce them later when they try to complete registration
2542	// note in particular that with skip-server-password, you can give the wrong server
2543	// password here, then successfully SASL and be admitted
2544	return false
2545}
2546
2547// PING [params...]
2548func pingHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2549	rb.Add(nil, server.name, "PONG", server.name, msg.Params[0])
2550	return false
2551}
2552
2553// PONG [params...]
2554func pongHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2555	// client gets touched when they send this command, so we don't need to do anything
2556	return false
2557}
2558
2559// QUIT [<reason>]
2560func quitHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2561	reason := "Quit"
2562	if len(msg.Params) > 0 {
2563		reason += ": " + msg.Params[0]
2564	}
2565	client.Quit(reason, rb.session)
2566	return true
2567}
2568
2569// REGISTER < account | * > < email | * > <password>
2570func registerHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) (exiting bool) {
2571	accountName := client.Nick()
2572	if accountName == "*" {
2573		accountName = client.preregNick
2574	}
2575
2576	switch msg.Params[0] {
2577	case "*", accountName:
2578		// ok
2579	default:
2580		rb.Add(nil, server.name, "FAIL", "REGISTER", "ACCOUNTNAME_MUST_BE_NICK", utils.SafeErrorParam(msg.Params[0]), client.t("You may only register your nickname as your account name"))
2581		return
2582	}
2583
2584	// check that accountName is valid as a non-final parameter;
2585	// this is necessary for us to be valid and it will prevent us from emitting invalid error lines
2586	nickErrorParam := utils.SafeErrorParam(accountName)
2587	if accountName == "*" || accountName != nickErrorParam {
2588		rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_USERNAME", nickErrorParam, client.t("Username invalid or not given"))
2589		return
2590	}
2591
2592	config := server.Config()
2593	if !config.Accounts.Registration.Enabled {
2594		rb.Add(nil, server.name, "FAIL", "REGISTER", "DISALLOWED", accountName, client.t("Account registration is disabled"))
2595		return
2596	}
2597	if !client.registered && !config.Accounts.Registration.AllowBeforeConnect {
2598		rb.Add(nil, server.name, "FAIL", "REGISTER", "COMPLETE_CONNECTION_REQUIRED", accountName, client.t("You must complete the connection before registering your account"))
2599		return
2600	}
2601	if client.registerCmdSent || client.Account() != "" {
2602		rb.Add(nil, server.name, "FAIL", "REGISTER", "ALREADY_REGISTERED", accountName, client.t("You have already registered or attempted to register"))
2603		return
2604	}
2605
2606	callbackNamespace, callbackValue, err := parseCallback(msg.Params[1], config)
2607	if err != nil {
2608		rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_EMAIL", accountName, client.t("A valid e-mail address is required"))
2609		return
2610	}
2611
2612	err = server.accounts.Register(client, accountName, callbackNamespace, callbackValue, msg.Params[2], rb.session.certfp)
2613	switch err {
2614	case nil:
2615		if callbackNamespace == "*" {
2616			err := server.accounts.Verify(client, accountName, "")
2617			if err == nil {
2618				if client.registered {
2619					if !fixupNickEqualsAccount(client, rb, config, "") {
2620						err = errNickAccountMismatch
2621					}
2622				}
2623				if err == nil {
2624					rb.Add(nil, server.name, "REGISTER", "SUCCESS", accountName, client.t("Account successfully registered"))
2625					sendSuccessfulRegResponse(nil, client, rb)
2626				}
2627			}
2628			if err != nil {
2629				server.logger.Error("internal", "accounts", "failed autoverification", accountName, err.Error())
2630				rb.Add(nil, server.name, "FAIL", "REGISTER", "UNKNOWN_ERROR", client.t("An error occurred"))
2631			}
2632		} else {
2633			rb.Add(nil, server.name, "REGISTER", "VERIFICATION_REQUIRED", accountName, fmt.Sprintf(client.t("Account created, pending verification; verification code has been sent to %s"), callbackValue))
2634			client.registerCmdSent = true
2635		}
2636	case errAccountAlreadyRegistered, errAccountAlreadyUnregistered, errAccountMustHoldNick:
2637		rb.Add(nil, server.name, "FAIL", "REGISTER", "USERNAME_EXISTS", accountName, client.t("Username is already registered or otherwise unavailable"))
2638	case errAccountBadPassphrase:
2639		rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_PASSWORD", accountName, client.t("Password was invalid"))
2640	default:
2641		if emailError := registrationCallbackErrorText(config, client, err); emailError != "" {
2642			rb.Add(nil, server.name, "FAIL", "REGISTER", "UNACCEPTABLE_EMAIL", accountName, emailError)
2643		} else {
2644			rb.Add(nil, server.name, "FAIL", "REGISTER", "UNKNOWN_ERROR", accountName, client.t("Could not register"))
2645		}
2646	}
2647	return
2648}
2649
2650// VERIFY <account> <code>
2651func verifyHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) (exiting bool) {
2652	config := server.Config()
2653	if !config.Accounts.Registration.Enabled {
2654		rb.Add(nil, server.name, "FAIL", "VERIFY", "DISALLOWED", client.t("Account registration is disabled"))
2655		return
2656	}
2657	if !client.registered && !config.Accounts.Registration.AllowBeforeConnect {
2658		rb.Add(nil, server.name, "FAIL", "VERIFY", "DISALLOWED", client.t("You must complete the connection before verifying your account"))
2659		return
2660	}
2661	if client.Account() != "" {
2662		rb.Add(nil, server.name, "FAIL", "VERIFY", "ALREADY_REGISTERED", client.t("You have already registered or attempted to register"))
2663		return
2664	}
2665
2666	accountName, verificationCode := msg.Params[0], msg.Params[1]
2667	err := server.accounts.Verify(client, accountName, verificationCode)
2668	if err == nil && client.registered {
2669		if !fixupNickEqualsAccount(client, rb, config, "") {
2670			err = errNickAccountMismatch
2671		}
2672	}
2673	switch err {
2674	case nil:
2675		rb.Add(nil, server.name, "VERIFY", "SUCCESS", accountName, client.t("Account successfully registered"))
2676		sendSuccessfulRegResponse(nil, client, rb)
2677	case errAccountVerificationInvalidCode:
2678		rb.Add(nil, server.name, "FAIL", "VERIFY", "INVALID_CODE", client.t("Invalid verification code"))
2679	default:
2680		rb.Add(nil, server.name, "FAIL", "VERIFY", "UNKNOWN_ERROR", client.t("Failed to verify account"))
2681	}
2682
2683	if err != nil && !client.registered {
2684		// XXX pre-registration clients are exempt from fakelag;
2685		// slow the client down to stop them spamming verify attempts
2686		time.Sleep(time.Second)
2687	}
2688
2689	return
2690}
2691
2692// REHASH
2693func rehashHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2694	nick := client.Nick()
2695	server.logger.Info("server", "REHASH command used by", nick)
2696	err := server.rehash()
2697
2698	if err == nil {
2699		// we used to send RPL_REHASHING here but i don't think it really makes sense
2700		// in the labeled-response world, since the intent is "rehash in progress" but
2701		// it won't display until the rehash is actually complete
2702		// TODO all operators should get a notice of some kind here
2703		rb.Notice(client.t("Rehash complete"))
2704	} else {
2705		rb.Add(nil, server.name, ERR_UNKNOWNERROR, nick, "REHASH", err.Error())
2706	}
2707	return false
2708}
2709
2710// RELAYMSG <channel> <spoofed nick> :<message>
2711func relaymsgHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) (result bool) {
2712	config := server.Config()
2713	if !config.Server.Relaymsg.Enabled {
2714		rb.Add(nil, server.name, "FAIL", "RELAYMSG", "NOT_ENABLED", client.t("RELAYMSG has been disabled"))
2715		return false
2716	}
2717
2718	channel := server.channels.Get(msg.Params[0])
2719	if channel == nil {
2720		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(msg.Params[0]), client.t("No such channel"))
2721		return false
2722	}
2723
2724	allowedToRelay := client.HasRoleCapabs("relaymsg") || (config.Server.Relaymsg.AvailableToChanops && channel.ClientIsAtLeast(client, modes.ChannelOperator))
2725	if !allowedToRelay {
2726		rb.Add(nil, server.name, "FAIL", "RELAYMSG", "PRIVS_NEEDED", client.t("You cannot relay messages to this channel"))
2727		return false
2728	}
2729
2730	rawMessage := msg.Params[2]
2731	if strings.TrimSpace(rawMessage) == "" {
2732		rb.Add(nil, server.name, "FAIL", "RELAYMSG", "BLANK_MSG", client.t("The message must not be blank"))
2733		return false
2734	}
2735	message := utils.MakeMessage(rawMessage)
2736
2737	nick := msg.Params[1]
2738	_, err := CasefoldName(nick)
2739	if err != nil {
2740		rb.Add(nil, server.name, "FAIL", "RELAYMSG", "INVALID_NICK", client.t("Invalid nickname"))
2741		return false
2742	}
2743	if !config.isRelaymsgIdentifier(nick) {
2744		rb.Add(nil, server.name, "FAIL", "RELAYMSG", "INVALID_NICK", fmt.Sprintf(client.t("Relayed nicknames MUST contain a relaymsg separator from this set: %s"), config.Server.Relaymsg.Separators))
2745		return false
2746	}
2747	if channel.relayNickMuted(nick) {
2748		rb.Add(nil, server.name, "FAIL", "RELAYMSG", "BANNED", fmt.Sprintf(client.t("%s is banned from relaying to the channel"), nick))
2749		return false
2750	}
2751
2752	details := client.Details()
2753	// #1647: we need to publish a full NUH. send ~u (or the configured alternative)
2754	// as the user/ident, and send the relayer's hostname as the hostname:
2755	ident := config.Server.CoerceIdent
2756	if ident == "" {
2757		ident = "~u"
2758	}
2759	// #1661: if the bot has its own account, use the account cloak,
2760	// otherwise fall back to the hostname (which may be IP-derived)
2761	hostname := details.hostname
2762	if details.accountName != "" {
2763		hostname = config.Server.Cloaks.ComputeAccountCloak(details.accountName)
2764	}
2765	nuh := fmt.Sprintf("%s!%s@%s", nick, ident, hostname)
2766
2767	channel.AddHistoryItem(history.Item{
2768		Type:    history.Privmsg,
2769		Message: message,
2770		Nick:    nuh,
2771	}, "")
2772
2773	// 3 possibilities for tags:
2774	// no tags, the relaymsg tag only, or the relaymsg tag together with all client-only tags
2775	relayTag := map[string]string{
2776		caps.RelaymsgTagName: details.nick,
2777	}
2778	clientOnlyTags := msg.ClientOnlyTags()
2779	var fullTags map[string]string
2780	if len(clientOnlyTags) == 0 {
2781		fullTags = relayTag
2782	} else {
2783		fullTags = make(map[string]string, 1+len(clientOnlyTags))
2784		fullTags[caps.RelaymsgTagName] = details.nick
2785		for t, v := range clientOnlyTags {
2786			fullTags[t] = v
2787		}
2788	}
2789
2790	// actually send the message
2791	channelName := channel.Name()
2792	for _, member := range channel.Members() {
2793		for _, session := range member.Sessions() {
2794			var tagsToUse map[string]string
2795			if session.capabilities.Has(caps.MessageTags) {
2796				tagsToUse = fullTags
2797			} else if session.capabilities.Has(caps.Relaymsg) {
2798				tagsToUse = relayTag
2799			}
2800
2801			if session == rb.session {
2802				rb.AddSplitMessageFromClient(nuh, "*", false, tagsToUse, "PRIVMSG", channelName, message)
2803			} else {
2804				session.sendSplitMsgFromClientInternal(false, nuh, "*", false, tagsToUse, "PRIVMSG", channelName, message)
2805			}
2806		}
2807	}
2808	return false
2809}
2810
2811// RENAME <oldchan> <newchan> [<reason>]
2812func renameHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2813	oldName, newName := msg.Params[0], msg.Params[1]
2814	var reason string
2815	if 2 < len(msg.Params) {
2816		reason = msg.Params[2]
2817	}
2818
2819	channel := server.channels.Get(oldName)
2820	if channel == nil {
2821		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(oldName), client.t("No such channel"))
2822		return false
2823	}
2824	oldName = channel.Name()
2825
2826	if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("chanreg")) {
2827		rb.Add(nil, server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), oldName, client.t("You're not a channel operator"))
2828		return false
2829	}
2830
2831	founder := channel.Founder()
2832	if founder != "" && founder != client.Account() {
2833		rb.Add(nil, server.name, "FAIL", "RENAME", "CANNOT_RENAME", oldName, utils.SafeErrorParam(newName), client.t("Only channel founders can change registered channels"))
2834		return false
2835	}
2836
2837	config := server.Config()
2838	status, _, _ := channel.historyStatus(config)
2839	if status == HistoryPersistent {
2840		rb.Add(nil, server.name, "FAIL", "RENAME", "CANNOT_RENAME", oldName, utils.SafeErrorParam(newName), client.t("Channels with persistent history cannot be renamed"))
2841		return false
2842	}
2843
2844	// perform the channel rename
2845	err := server.channels.Rename(oldName, newName)
2846	if err == errInvalidChannelName {
2847		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(newName), client.t(err.Error()))
2848	} else if err == errChannelNameInUse || err == errConfusableIdentifier {
2849		rb.Add(nil, server.name, "FAIL", "RENAME", "CHANNEL_NAME_IN_USE", oldName, utils.SafeErrorParam(newName), client.t(err.Error()))
2850	} else if err != nil {
2851		rb.Add(nil, server.name, "FAIL", "RENAME", "CANNOT_RENAME", oldName, utils.SafeErrorParam(newName), client.t("Cannot rename channel"))
2852	}
2853	if err != nil {
2854		return false
2855	}
2856
2857	// send RENAME messages
2858	clientPrefix := client.NickMaskString()
2859	for _, mcl := range channel.Members() {
2860		mDetails := mcl.Details()
2861		for _, mSession := range mcl.Sessions() {
2862			targetRb := rb
2863			targetPrefix := clientPrefix
2864			if mSession != rb.session {
2865				targetRb = NewResponseBuffer(mSession)
2866				targetPrefix = mDetails.nickMask
2867			}
2868			if mSession.capabilities.Has(caps.ChannelRename) {
2869				if reason != "" {
2870					targetRb.Add(nil, clientPrefix, "RENAME", oldName, newName, reason)
2871				} else {
2872					targetRb.Add(nil, clientPrefix, "RENAME", oldName, newName)
2873				}
2874			} else {
2875				if reason != "" {
2876					targetRb.Add(nil, targetPrefix, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
2877				} else {
2878					targetRb.Add(nil, targetPrefix, "PART", oldName, mcl.t("Channel renamed"))
2879				}
2880				if mSession.capabilities.Has(caps.ExtendedJoin) {
2881					targetRb.Add(nil, targetPrefix, "JOIN", newName, mDetails.accountName, mDetails.realname)
2882				} else {
2883					targetRb.Add(nil, targetPrefix, "JOIN", newName)
2884				}
2885				channel.SendTopic(mcl, targetRb, false)
2886				channel.Names(mcl, targetRb)
2887			}
2888			if mcl != client {
2889				targetRb.Send(false)
2890			}
2891		}
2892	}
2893
2894	return false
2895}
2896
2897// SANICK <oldnick> <nickname>
2898func sanickHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2899	targetNick := msg.Params[0]
2900	target := server.clients.Get(targetNick)
2901	if target == nil {
2902		rb.Add(nil, server.name, "FAIL", "SANICK", "NO_SUCH_NICKNAME", utils.SafeErrorParam(targetNick), client.t("No such nick"))
2903		return false
2904	}
2905	performNickChange(server, client, target, nil, msg.Params[1], rb)
2906	return false
2907}
2908
2909// SCENE <target> <message>
2910func sceneHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2911	target := msg.Params[0]
2912	message := msg.Params[1:]
2913
2914	sendRoleplayMessage(server, client, "", target, true, false, message, rb)
2915
2916	return false
2917}
2918
2919// SETNAME <realname>
2920func setnameHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2921	realname := msg.Params[0]
2922	if len(msg.Params) != 1 {
2923		// workaround for clients that turn unknown commands into raw IRC lines,
2924		// so you can do `/setname Jane Doe` in the client and get the expected result
2925		realname = strings.Join(msg.Params, " ")
2926	}
2927	if realname == "" {
2928		rb.Add(nil, server.name, "FAIL", "SETNAME", "INVALID_REALNAME", client.t("Realname is not valid"))
2929		return false
2930	}
2931
2932	client.SetRealname(realname)
2933	details := client.Details()
2934
2935	// alert friends
2936	now := time.Now().UTC()
2937	friends := client.FriendsMonitors(caps.SetName)
2938	delete(friends, rb.session)
2939	isBot := client.HasMode(modes.Bot)
2940	for session := range friends {
2941		session.sendFromClientInternal(false, now, "", details.nickMask, details.accountName, isBot, nil, "SETNAME", details.realname)
2942	}
2943	// respond to the user unconditionally, even if they don't have the cap
2944	rb.AddFromClient(now, "", details.nickMask, details.accountName, isBot, nil, "SETNAME", details.realname)
2945	return false
2946}
2947
2948// SUMMON [parameters]
2949func summonHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2950	rb.Add(nil, server.name, ERR_SUMMONDISABLED, client.Nick(), client.t("SUMMON has been disabled"))
2951	return false
2952}
2953
2954// TIME
2955func timeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2956	rb.Add(nil, server.name, RPL_TIME, client.nick, server.name, time.Now().UTC().Format(time.RFC1123))
2957	return false
2958}
2959
2960// TOPIC <channel> [<topic>]
2961func topicHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2962	channel := server.channels.Get(msg.Params[0])
2963	if channel == nil {
2964		rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, utils.SafeErrorParam(msg.Params[0]), client.t("No such channel"))
2965		return false
2966	}
2967
2968	if len(msg.Params) > 1 {
2969		channel.SetTopic(client, msg.Params[1], rb)
2970	} else {
2971		channel.SendTopic(client, rb, true)
2972	}
2973	return false
2974}
2975
2976// UNDLINE <ip>|<net>
2977func unDLineHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
2978	// check oper permissions
2979	oper := client.Oper()
2980	if !oper.HasRoleCapab("ban") {
2981		rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs"))
2982		return false
2983	}
2984
2985	// get host
2986	hostString := msg.Params[0]
2987
2988	// check host
2989	hostNet, err := flatip.ParseToNormalizedNet(hostString)
2990
2991	if err != nil {
2992		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Could not parse IP address or CIDR network"))
2993		return false
2994	}
2995
2996	err = server.dlines.RemoveNetwork(hostNet)
2997
2998	if err != nil {
2999		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, fmt.Sprintf(client.t("Could not remove ban [%s]"), err.Error()))
3000		return false
3001	}
3002
3003	hostString = hostNet.String()
3004	rb.Notice(fmt.Sprintf(client.t("Removed D-Line for %s"), hostString))
3005	server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed D-Line for %s"), client.nick, hostString))
3006	return false
3007}
3008
3009// UNKLINE <mask>
3010func unKLineHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3011	details := client.Details()
3012	// check oper permissions
3013	oper := client.Oper()
3014	if !oper.HasRoleCapab("ban") {
3015		rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs"))
3016		return false
3017	}
3018
3019	// get host
3020	mask := msg.Params[0]
3021	mask, err := CanonicalizeMaskWildcard(mask)
3022	if err != nil {
3023		rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
3024		return false
3025	}
3026
3027	err = server.klines.RemoveMask(mask)
3028
3029	if err != nil {
3030		rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, fmt.Sprintf(client.t("Could not remove ban [%s]"), err.Error()))
3031		return false
3032	}
3033
3034	rb.Notice(fmt.Sprintf(client.t("Removed K-Line for %s"), mask))
3035	server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed K-Line for %s"), details.nick, mask))
3036	return false
3037}
3038
3039// USER <username> * 0 <realname>
3040func userHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3041	if client.registered {
3042		rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.Nick(), client.t("You may not reregister"))
3043		return false
3044	}
3045
3046	username, realname := msg.Params[0], msg.Params[3]
3047	if len(realname) == 0 {
3048		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), "USER", client.t("Not enough parameters"))
3049		return false
3050	}
3051
3052	// #843: we accept either: `USER user:pass@clientid` or `USER user@clientid`
3053	if strudelIndex := strings.IndexByte(username, '@'); strudelIndex != -1 {
3054		username, rb.session.deviceID = username[:strudelIndex], username[strudelIndex+1:]
3055		if colonIndex := strings.IndexByte(username, ':'); colonIndex != -1 {
3056			var password string
3057			username, password = username[:colonIndex], username[colonIndex+1:]
3058			err := server.accounts.AuthenticateByPassphrase(client, username, password)
3059			if err == nil {
3060				sendSuccessfulAccountAuth(nil, client, rb, true)
3061			} else {
3062				// this is wrong, but send something for debugging that will show up in a raw transcript
3063				rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), client.t("SASL authentication failed"))
3064			}
3065		}
3066	}
3067
3068	err := client.SetNames(username, realname, false)
3069	if err == errInvalidUsername {
3070		// if client's using a unicode nick or something weird, let's just set 'em up with a stock username instead.
3071		// fixes clients that just use their nick as a username so they can still use the interesting nick
3072		if client.preregNick == username {
3073			client.SetNames("user", realname, false)
3074		} else {
3075			rb.Add(nil, server.name, ERR_INVALIDUSERNAME, client.Nick(), client.t("Malformed username"))
3076		}
3077	}
3078
3079	return false
3080}
3081
3082// does `target` have an operator status that is visible to `client`?
3083func operStatusVisible(client, target *Client, hasPrivs bool) bool {
3084	targetOper := target.Oper()
3085	if targetOper == nil {
3086		return false
3087	}
3088	if client == target || hasPrivs {
3089		return true
3090	}
3091	return !targetOper.Hidden
3092}
3093
3094// USERHOST <nickname>{ <nickname>}
3095func userhostHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3096	hasPrivs := client.HasMode(modes.Operator)
3097	returnedClients := make(ClientSet)
3098
3099	var tl utils.TokenLineBuilder
3100	tl.Initialize(400, " ")
3101	for i, nickname := range msg.Params {
3102		if i >= 10 {
3103			break
3104		}
3105
3106		target := server.clients.Get(nickname)
3107		if target == nil {
3108			continue
3109		}
3110		// to prevent returning multiple results for a single nick
3111		if returnedClients.Has(target) {
3112			continue
3113		}
3114		returnedClients.Add(target)
3115
3116		var isOper, isAway string
3117
3118		if operStatusVisible(client, target, hasPrivs) {
3119			isOper = "*"
3120		}
3121		if away, _ := target.Away(); away {
3122			isAway = "-"
3123		} else {
3124			isAway = "+"
3125		}
3126		details := target.Details()
3127		tl.Add(fmt.Sprintf("%s%s=%s%s@%s", details.nick, isOper, isAway, details.username, details.hostname))
3128	}
3129
3130	lines := tl.Lines()
3131	if lines == nil {
3132		lines = []string{""}
3133	}
3134	nick := client.Nick()
3135	for _, line := range lines {
3136		rb.Add(nil, client.server.name, RPL_USERHOST, nick, line)
3137	}
3138
3139	return false
3140}
3141
3142// USERS [parameters]
3143func usersHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3144	rb.Add(nil, server.name, ERR_USERSDISABLED, client.Nick(), client.t("USERS has been disabled"))
3145	return false
3146}
3147
3148// VERSION
3149func versionHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3150	rb.Add(nil, server.name, RPL_VERSION, client.nick, Ver, server.name)
3151	server.RplISupport(client, rb)
3152	return false
3153}
3154
3155// WEBIRC <password> <gateway> <hostname> <ip> [:flag1 flag2=x flag3]
3156func webircHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3157	// only allow unregistered clients to use this command
3158	if client.registered || client.proxiedIP != nil {
3159		return false
3160	}
3161
3162	// process flags
3163	var secure bool
3164	if 4 < len(msg.Params) {
3165		for _, x := range strings.Split(msg.Params[4], " ") {
3166			// split into key=value
3167			var key string
3168			if strings.Contains(x, "=") {
3169				y := strings.SplitN(x, "=", 2)
3170				key, _ = y[0], y[1]
3171			} else {
3172				key = x
3173			}
3174
3175			lkey := strings.ToLower(key)
3176			if lkey == "tls" || lkey == "secure" {
3177				// only accept "tls" flag if the gateway's connection to us is secure as well
3178				if client.HasMode(modes.TLS) || client.realIP.IsLoopback() {
3179					secure = true
3180				}
3181			}
3182		}
3183	}
3184
3185	givenPassword := []byte(msg.Params[0])
3186	for _, info := range server.Config().Server.WebIRC {
3187		if utils.IPInNets(client.realIP, info.allowedNets) {
3188			// confirm password and/or fingerprint
3189			if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil {
3190				continue
3191			}
3192			if info.Certfp != "" && info.Certfp != rb.session.certfp {
3193				continue
3194			}
3195
3196			err, quitMsg := client.ApplyProxiedIP(rb.session, net.ParseIP(msg.Params[3]), secure)
3197			if err != nil {
3198				client.Quit(quitMsg, rb.session)
3199				return true
3200			} else {
3201				return false
3202			}
3203		}
3204	}
3205
3206	client.Quit(client.t("WEBIRC command is not usable from your address or incorrect password given"), rb.session)
3207	return true
3208}
3209
3210type whoxFields uint32 // bitset to hold the WHOX field values, 'a' through 'z'
3211
3212func (fields whoxFields) Add(field rune) (result whoxFields) {
3213	index := int(field) - int('a')
3214	if 0 <= index && index < 26 {
3215		return fields | (1 << index)
3216	} else {
3217		return fields
3218	}
3219}
3220
3221func (fields whoxFields) Has(field rune) bool {
3222	index := int(field) - int('a')
3223	if 0 <= index && index < 26 {
3224		return (fields & (1 << index)) != 0
3225	} else {
3226		return false
3227	}
3228}
3229
3230// rplWhoReply returns the WHO(X) reply between one user and another channel/user.
3231// who format:
3232// <channel> <user> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] :<hopcount> <real name>
3233// whox format:
3234// <type> <channel> <user> <ip> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] <hops> <idle> <account> <rank> :<real name>
3235func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *ResponseBuffer, canSeeIPs, canSeeOpers, includeRFlag, isWhox bool, fields whoxFields, whoType string) {
3236	params := []string{client.Nick()}
3237
3238	details := target.Details()
3239
3240	if fields.Has('t') {
3241		params = append(params, whoType)
3242	}
3243	if fields.Has('c') {
3244		fChannel := "*"
3245		if channel != nil {
3246			fChannel = channel.name
3247		}
3248		params = append(params, fChannel)
3249	}
3250	if fields.Has('u') {
3251		params = append(params, details.username)
3252	}
3253	if fields.Has('i') {
3254		fIP := "255.255.255.255"
3255		if canSeeIPs || client == target {
3256			// you can only see a target's IP if they're you or you're an oper
3257			fIP = target.IPString()
3258		}
3259		params = append(params, fIP)
3260	}
3261	if fields.Has('h') {
3262		params = append(params, details.hostname)
3263	}
3264	if fields.Has('s') {
3265		params = append(params, target.server.name)
3266	}
3267	if fields.Has('n') {
3268		params = append(params, details.nick)
3269	}
3270	if fields.Has('f') { // "flags" (away + oper state + channel status prefix + bot)
3271		var flags strings.Builder
3272		if away, _ := target.Away(); away {
3273			flags.WriteRune('G') // Gone
3274		} else {
3275			flags.WriteRune('H') // Here
3276		}
3277
3278		if target.HasMode(modes.Operator) && operStatusVisible(client, target, canSeeOpers) {
3279			flags.WriteRune('*')
3280		}
3281
3282		if channel != nil {
3283			flags.WriteString(channel.ClientPrefixes(target, rb.session.capabilities.Has(caps.MultiPrefix)))
3284		}
3285
3286		if target.HasMode(modes.Bot) {
3287			flags.WriteRune('B')
3288		}
3289
3290		if includeRFlag && details.account != "" {
3291			flags.WriteRune('r')
3292		}
3293
3294		params = append(params, flags.String())
3295
3296	}
3297	if fields.Has('d') { // server hops from us to target
3298		params = append(params, "0")
3299	}
3300	if fields.Has('l') {
3301		params = append(params, fmt.Sprintf("%d", target.IdleSeconds()))
3302	}
3303	if fields.Has('a') {
3304		fAccount := "0"
3305		if details.accountName != "*" {
3306			// WHOX uses "0" to mean "no account"
3307			fAccount = details.accountName
3308		}
3309		params = append(params, fAccount)
3310	}
3311	if fields.Has('o') { // target's channel power level
3312		//TODO: implement this
3313		params = append(params, "0")
3314	}
3315	if fields.Has('r') {
3316		params = append(params, details.realname)
3317	}
3318
3319	numeric := RPL_WHOSPCRPL
3320	if !isWhox {
3321		numeric = RPL_WHOREPLY
3322		// if this isn't WHOX, stick hops + realname at the end
3323		params = append(params, "0 "+details.realname)
3324	}
3325
3326	rb.Add(nil, client.server.name, numeric, params...)
3327}
3328
3329// WHO <mask> [<filter>%<fields>,<type>]
3330func whoHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3331	mask := msg.Params[0]
3332	var err error
3333	if mask == "" {
3334		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "WHO", client.t("First param must be a mask or channel"))
3335		return false
3336	} else if mask[0] == '#' {
3337		mask, err = CasefoldChannel(msg.Params[0])
3338	} else {
3339		mask, err = CanonicalizeMaskWildcard(mask)
3340	}
3341
3342	if err != nil {
3343		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "WHO", client.t("Mask isn't valid"))
3344		return false
3345	}
3346
3347	// include the r flag only if nick and account are synonymous
3348	config := server.Config()
3349	includeRFlag := config.Accounts.NickReservation.Enabled &&
3350		config.Accounts.NickReservation.Method == NickEnforcementStrict &&
3351		!config.Accounts.NickReservation.AllowCustomEnforcement &&
3352		config.Accounts.NickReservation.ForceNickEqualsAccount
3353
3354	sFields := "cuhsnf"
3355	whoType := "0"
3356	isWhox := false
3357	if len(msg.Params) > 1 && strings.Contains(msg.Params[1], "%") {
3358		isWhox = true
3359		whoxData := msg.Params[1]
3360		fieldStart := strings.Index(whoxData, "%")
3361		sFields = whoxData[fieldStart+1:]
3362
3363		typeIndex := strings.Index(sFields, ",")
3364		if typeIndex > -1 && typeIndex < (len(sFields)-1) { // make sure there's , and a value after it
3365			whoType = sFields[typeIndex+1:]
3366			sFields = strings.ToLower(sFields[:typeIndex])
3367		}
3368	}
3369	var fields whoxFields
3370	for _, field := range sFields {
3371		fields = fields.Add(field)
3372	}
3373
3374	//TODO(dan): is this used and would I put this param in the Modern doc?
3375	// if not, can we remove it?
3376	//var operatorOnly bool
3377	//if len(msg.Params) > 1 && msg.Params[1] == "o" {
3378	//	operatorOnly = true
3379	//}
3380
3381	oper := client.Oper()
3382	hasPrivs := oper.HasRoleCapab("sajoin")
3383	canSeeIPs := oper.HasRoleCapab("ban")
3384	if mask[0] == '#' {
3385		channel := server.channels.Get(mask)
3386		if channel != nil {
3387			isJoined := channel.hasClient(client)
3388			if !channel.flags.HasMode(modes.Secret) || isJoined || hasPrivs {
3389				var members []*Client
3390				if hasPrivs {
3391					members = channel.Members()
3392				} else {
3393					members = channel.auditoriumFriends(client)
3394				}
3395				for _, member := range members {
3396					if !member.HasMode(modes.Invisible) || isJoined || hasPrivs {
3397						client.rplWhoReply(channel, member, rb, canSeeIPs, oper != nil, includeRFlag, isWhox, fields, whoType)
3398					}
3399				}
3400			}
3401		}
3402	} else {
3403		// Construct set of channels the client is in.
3404		userChannels := make(ChannelSet)
3405		for _, channel := range client.Channels() {
3406			userChannels[channel] = empty{}
3407		}
3408
3409		// Another client is a friend if they share at least one channel, or they are the same client.
3410		isFriend := func(otherClient *Client) bool {
3411			if client == otherClient {
3412				return true
3413			}
3414
3415			for _, channel := range otherClient.Channels() {
3416				if channel.flags.HasMode(modes.Auditorium) {
3417					return false // TODO this should respect +v etc.
3418				}
3419				if _, present := userChannels[channel]; present {
3420					return true
3421				}
3422			}
3423			return false
3424		}
3425
3426		for mclient := range server.clients.FindAll(mask) {
3427			if hasPrivs || !mclient.HasMode(modes.Invisible) || isFriend(mclient) {
3428				client.rplWhoReply(nil, mclient, rb, canSeeIPs, oper != nil, includeRFlag, isWhox, fields, whoType)
3429			}
3430		}
3431	}
3432
3433	rb.Add(nil, server.name, RPL_ENDOFWHO, client.nick, mask, client.t("End of WHO list"))
3434	return false
3435}
3436
3437// WHOIS [<target>] <mask>{,<mask>}
3438func whoisHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3439	var masksString string
3440	//var target string
3441
3442	if len(msg.Params) > 1 {
3443		//target = msg.Params[0]
3444		masksString = msg.Params[1]
3445	} else {
3446		masksString = msg.Params[0]
3447	}
3448
3449	handleService := func(nick string) bool {
3450		cfnick, _ := CasefoldName(nick)
3451		service, ok := OragonoServices[cfnick]
3452		hostname := "localhost"
3453		config := server.Config()
3454		if config.Server.OverrideServicesHostname != "" {
3455			hostname = config.Server.OverrideServicesHostname
3456		}
3457		if !ok {
3458			return false
3459		}
3460		clientNick := client.Nick()
3461		rb.Add(nil, client.server.name, RPL_WHOISUSER, clientNick, service.Name, service.Name, hostname, "*", fmt.Sprintf(client.t("Network service, for more info /msg %s HELP"), service.Name))
3462		// #1080:
3463		rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, clientNick, service.Name, client.t("is a network service"))
3464		// hehe
3465		if client.HasMode(modes.TLS) {
3466			rb.Add(nil, client.server.name, RPL_WHOISSECURE, clientNick, service.Name, client.t("is using a secure connection"))
3467		}
3468		return true
3469	}
3470
3471	hasPrivs := client.HasRoleCapabs("samode")
3472	if hasPrivs {
3473		for _, mask := range strings.Split(masksString, ",") {
3474			matches := server.clients.FindAll(mask)
3475			if len(matches) == 0 && !handleService(mask) {
3476				rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.Nick(), utils.SafeErrorParam(mask), client.t("No such nick"))
3477				continue
3478			}
3479			for mclient := range matches {
3480				client.getWhoisOf(mclient, hasPrivs, rb)
3481			}
3482		}
3483	} else {
3484		// only get the first request; also require a nick, not a mask
3485		nick := strings.Split(masksString, ",")[0]
3486		mclient := server.clients.Get(nick)
3487		if mclient != nil {
3488			client.getWhoisOf(mclient, hasPrivs, rb)
3489		} else if !handleService(nick) {
3490			rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.Nick(), utils.SafeErrorParam(masksString), client.t("No such nick"))
3491		}
3492		// fall through, ENDOFWHOIS is always sent
3493	}
3494	rb.Add(nil, server.name, RPL_ENDOFWHOIS, client.nick, utils.SafeErrorParam(masksString), client.t("End of /WHOIS list"))
3495	return false
3496}
3497
3498// WHOWAS <nickname> [<count> [<server>]]
3499func whowasHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3500	nicknames := strings.Split(msg.Params[0], ",")
3501
3502	// 0 means "all the entries", as does a negative number
3503	var count int
3504	if len(msg.Params) > 1 {
3505		count, _ = strconv.Atoi(msg.Params[1])
3506		if count < 0 {
3507			count = 0
3508		}
3509	}
3510	cnick := client.Nick()
3511	canSeeIP := client.Oper().HasRoleCapab("ban")
3512	for _, nickname := range nicknames {
3513		results := server.whoWas.Find(nickname, count)
3514		if len(results) == 0 {
3515			rb.Add(nil, server.name, ERR_WASNOSUCHNICK, cnick, utils.SafeErrorParam(nickname), client.t("There was no such nickname"))
3516		} else {
3517			for _, whoWas := range results {
3518				rb.Add(nil, server.name, RPL_WHOWASUSER, cnick, whoWas.nick, whoWas.username, whoWas.hostname, "*", whoWas.realname)
3519				if canSeeIP {
3520					rb.Add(nil, server.name, RPL_WHOWASIP, cnick, whoWas.nick, fmt.Sprintf(client.t("was connecting from %s"), utils.IPStringToHostname(whoWas.ip.String())))
3521				}
3522			}
3523		}
3524		rb.Add(nil, server.name, RPL_ENDOFWHOWAS, cnick, utils.SafeErrorParam(nickname), client.t("End of WHOWAS"))
3525	}
3526	return false
3527}
3528
3529// ZNC <module> [params]
3530func zncHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3531	params := msg.Params[1:]
3532	// #1205: compatibility with Palaver, which sends `ZNC *playback :play ...`
3533	if len(params) == 1 && strings.IndexByte(params[0], ' ') != -1 {
3534		params = strings.Fields(params[0])
3535	}
3536	zncModuleHandler(client, msg.Params[0], params, rb)
3537	return false
3538}
3539
3540// fake handler for unknown commands
3541func unknownCommandHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3542	var message string
3543	if strings.HasPrefix(msg.Command, "/") {
3544		message = fmt.Sprintf(client.t("Unknown command; if you are using /QUOTE, the correct syntax is /QUOTE %s, not /QUOTE %s"),
3545			strings.TrimPrefix(msg.Command, "/"), msg.Command)
3546	} else {
3547		message = client.t("Unknown command")
3548	}
3549
3550	rb.Add(nil, server.name, ERR_UNKNOWNCOMMAND, client.Nick(), utils.SafeErrorParam(msg.Command), message)
3551	return false
3552}
3553
3554// fake handler for invalid utf8
3555func invalidUtf8Handler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
3556	rb.Add(nil, server.name, "FAIL", utils.SafeErrorParam(msg.Command), "INVALID_UTF8", client.t("Message rejected for containing invalid UTF-8"))
3557	return false
3558}
3559