hid: port MODE, STATS, LINKS, KILL
Now all the commands have been ported but we desperately need to parse a configuration file for additional settings yet.
This commit is contained in:
		
							
								
								
									
										584
									
								
								hid/main.go
									
									
									
									
									
								
							
							
						
						
									
										584
									
								
								hid/main.go
									
									
									
									
									
								
							@@ -18,6 +18,7 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
@@ -45,6 +46,11 @@ const (
 | 
			
		||||
	projectVersion = "0"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO: Consider using time.Time directly instead of storing Unix epoch
 | 
			
		||||
// timestamps with nanosecond precision. Despite carrying unnecessary timezone
 | 
			
		||||
// information, it also carries a monotonic reading of the time, which allows
 | 
			
		||||
// for more precise measurement of time differences.
 | 
			
		||||
 | 
			
		||||
// --- Utilities ---------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// Split a string by a set of UTF-8 delimiters, optionally ignoring empty items.
 | 
			
		||||
@@ -316,8 +322,6 @@ const (
 | 
			
		||||
	ircMaxMessageLength = 510
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO: Port the IRC token validation part as needed.
 | 
			
		||||
 | 
			
		||||
const reClassSpecial = "\\[\\]\\\\`_^{|}"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -330,6 +334,9 @@ var (
 | 
			
		||||
	reUsername = regexp.MustCompile(`^[^\0\r\n @]+$`)
 | 
			
		||||
 | 
			
		||||
	reChannelName = regexp.MustCompile(`^[^\0\7\r\n ,:]+$`)
 | 
			
		||||
	reKey         = regexp.MustCompile(`^[^\r\n\f\t\v ]{1,23}$`)
 | 
			
		||||
	reUserMask    = regexp.MustCompile(`^[^!@]+![^!@]+@[^@!]+$`)
 | 
			
		||||
	reFingerprint = regexp.MustCompile(`^[a-fA-F0-9]{64}$`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ircIsValidNickname(nickname string) bool {
 | 
			
		||||
@@ -346,6 +353,19 @@ func ircIsValidChannelName(name string) bool {
 | 
			
		||||
	return len(name) <= ircMaxChannelName && reChannelName.MatchString(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircIsValidKey(key string) bool {
 | 
			
		||||
	// XXX: Should be 7-bit as well but whatever.
 | 
			
		||||
	return reKey.MatchString(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircIsValidUserMask(mask string) bool {
 | 
			
		||||
	return reUserMask.MatchString(mask)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircIsValidFingerprint(fp string) bool {
 | 
			
		||||
	return reFingerprint.MatchString(fp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Clients (equals users) --------------------------------------------------
 | 
			
		||||
 | 
			
		||||
type connCloseWrite interface {
 | 
			
		||||
@@ -356,7 +376,7 @@ type connCloseWrite interface {
 | 
			
		||||
const ircSupportedUserModes = "aiwros"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ircUserModeInvisible = 1 << iota
 | 
			
		||||
	ircUserModeInvisible uint = 1 << iota
 | 
			
		||||
	ircUserModeRxWallops
 | 
			
		||||
	ircUserModeRestricted
 | 
			
		||||
	ircUserModeOperator
 | 
			
		||||
@@ -364,7 +384,7 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ircCapMultiPrefix = 1 << iota
 | 
			
		||||
	ircCapMultiPrefix uint = 1 << iota
 | 
			
		||||
	ircCapInviteNotify
 | 
			
		||||
	ircCapEchoMessage
 | 
			
		||||
	ircCapUserhostInNames
 | 
			
		||||
@@ -417,7 +437,7 @@ type client struct {
 | 
			
		||||
const ircSupportedChanModes = "ov" + "beI" + "imnqpst" + "kl"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ircChanModeInviteOnly = 1 << iota
 | 
			
		||||
	ircChanModeInviteOnly uint = 1 << iota
 | 
			
		||||
	ircChanModeModerated
 | 
			
		||||
	ircChanModeNoOutsideMsgs
 | 
			
		||||
	ircChanModeQuiet
 | 
			
		||||
@@ -447,11 +467,48 @@ type channel struct {
 | 
			
		||||
	inviteList    []string // exceptions from +I
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newChannel() *channel {
 | 
			
		||||
	return &channel{userLimit: -1}
 | 
			
		||||
}
 | 
			
		||||
func (ch *channel) getMode(discloseSecrets bool) string {
 | 
			
		||||
	var buf []byte
 | 
			
		||||
	if 0 != ch.modes&ircChanModeInviteOnly {
 | 
			
		||||
		buf = append(buf, 'i')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != ch.modes&ircChanModeModerated {
 | 
			
		||||
		buf = append(buf, 'm')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != ch.modes&ircChanModeNoOutsideMsgs {
 | 
			
		||||
		buf = append(buf, 'n')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != ch.modes&ircChanModeQuiet {
 | 
			
		||||
		buf = append(buf, 'q')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != ch.modes&ircChanModePrivate {
 | 
			
		||||
		buf = append(buf, 'p')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != ch.modes&ircChanModeSecret {
 | 
			
		||||
		buf = append(buf, 's')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != ch.modes&ircChanModeProtectedTopic {
 | 
			
		||||
		buf = append(buf, 'r')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
// TODO: Port struct channel methods.
 | 
			
		||||
	if ch.userLimit != -1 {
 | 
			
		||||
		buf = append(buf, 'l')
 | 
			
		||||
	}
 | 
			
		||||
	if ch.key != "" {
 | 
			
		||||
		buf = append(buf, 'k')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// XXX: Is it correct to split it? Try it on an existing implementation.
 | 
			
		||||
	if discloseSecrets {
 | 
			
		||||
		if ch.userLimit != -1 {
 | 
			
		||||
			buf = append(buf, fmt.Sprintf(" %d", ch.userLimit)...)
 | 
			
		||||
		}
 | 
			
		||||
		if ch.key != "" {
 | 
			
		||||
			buf = append(buf, fmt.Sprintf(" %s", ch.key)...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return string(buf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- IRC server context ------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
@@ -854,6 +911,7 @@ func (c *client) sendLUSERS() {
 | 
			
		||||
	c.sendReply(RPL_LUSERME, nUsers+nServices+nUnknown, 0 /* peer servers */)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Rename back to ircIsThisMe for consistency with kike.
 | 
			
		||||
func isThisMe(target string) bool {
 | 
			
		||||
	// Target servers can also be matched by their users
 | 
			
		||||
	if ircFnmatch(target, serverName) {
 | 
			
		||||
@@ -1237,7 +1295,6 @@ func ircChannelMulticast(ch *channel, msg string, except *client) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func ircModifyMode(mask *uint, mode uint, add bool) bool {
 | 
			
		||||
	orig := *mask
 | 
			
		||||
	if add {
 | 
			
		||||
@@ -1249,9 +1306,365 @@ func ircModifyMode(mask *uint, mode uint, add bool) bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircUpdateUserMode(c *client, newMode uint) {
 | 
			
		||||
	// TODO: Port, as well as all the other kike functions.
 | 
			
		||||
	oldMode := c.mode
 | 
			
		||||
	c.mode = newMode
 | 
			
		||||
 | 
			
		||||
	added, removed := newMode & ^oldMode, oldMode & ^newMode
 | 
			
		||||
 | 
			
		||||
	var diff []byte
 | 
			
		||||
	if added != 0 {
 | 
			
		||||
		diff = append(diff, '+')
 | 
			
		||||
		diff = ircAppendClientModes(added, diff)
 | 
			
		||||
	}
 | 
			
		||||
	if removed != 0 {
 | 
			
		||||
		diff = append(diff, '-')
 | 
			
		||||
		diff = ircAppendClientModes(removed, diff)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(diff) > 0 {
 | 
			
		||||
		c.sendf(":%s MODE %s :%s", c.nickname, c.nickname, string(diff))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
func ircHandleUserModeChange(c *client, modeString string) {
 | 
			
		||||
	newMode := c.mode
 | 
			
		||||
	adding := true
 | 
			
		||||
 | 
			
		||||
	for _, flag := range modeString {
 | 
			
		||||
		switch flag {
 | 
			
		||||
		case '+':
 | 
			
		||||
			adding = true
 | 
			
		||||
		case '-':
 | 
			
		||||
			adding = false
 | 
			
		||||
 | 
			
		||||
		case 'a':
 | 
			
		||||
			// Ignore, the client should use AWAY.
 | 
			
		||||
		case 'i':
 | 
			
		||||
			ircModifyMode(&newMode, ircUserModeInvisible, adding)
 | 
			
		||||
		case 'w':
 | 
			
		||||
			ircModifyMode(&newMode, ircUserModeRxWallops, adding)
 | 
			
		||||
		case 'r':
 | 
			
		||||
			// It's not possible ot un-restrict yourself.
 | 
			
		||||
			if adding {
 | 
			
		||||
				newMode |= ircUserModeRestricted
 | 
			
		||||
			}
 | 
			
		||||
		case 'o':
 | 
			
		||||
			if !adding {
 | 
			
		||||
				newMode &= ^ircUserModeOperator
 | 
			
		||||
			} else if operators[c.tlsCertFingerprint] {
 | 
			
		||||
				newMode |= ircUserModeOperator
 | 
			
		||||
			} else {
 | 
			
		||||
				c.sendf(":%s NOTICE %s :Either you're not using an TLS"+
 | 
			
		||||
					" client certificate, or the fingerprint doesn't match",
 | 
			
		||||
					serverName, c.nickname)
 | 
			
		||||
			}
 | 
			
		||||
		case 's':
 | 
			
		||||
			ircModifyMode(&newMode, ircUserModeRxServerNotices, adding)
 | 
			
		||||
		default:
 | 
			
		||||
			c.sendReply(ERR_UMODEUNKNOWNFLAG)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ircUpdateUserMode(c, newMode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendChannelList(c *client, channelName string, list []string,
 | 
			
		||||
	reply, endReply int) {
 | 
			
		||||
	for _, line := range list {
 | 
			
		||||
		c.sendReply(reply, channelName, line)
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(endReply, channelName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircCheckExpandUserMask(mask string) string {
 | 
			
		||||
	var result []byte
 | 
			
		||||
	result = append(result, mask...)
 | 
			
		||||
 | 
			
		||||
	// Make sure it is a complete mask.
 | 
			
		||||
	if bytes.IndexByte(result, '!') < 0 {
 | 
			
		||||
		result = append(result, "!*"...)
 | 
			
		||||
	}
 | 
			
		||||
	if bytes.IndexByte(result, '@') < 0 {
 | 
			
		||||
		result = append(result, "@*"...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// And validate whatever the result is.
 | 
			
		||||
	s := string(result)
 | 
			
		||||
	if !ircIsValidUserMask(s) {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
 | 
			
		||||
// Channel MODE command handling. This is by far the worst command to implement
 | 
			
		||||
// from the whole RFC; don't blame me if it doesn't work exactly as expected.
 | 
			
		||||
 | 
			
		||||
type modeProcessor struct {
 | 
			
		||||
	params []string // mode string parameters
 | 
			
		||||
 | 
			
		||||
	c       *client  // who does the changes
 | 
			
		||||
	ch      *channel // the channel we're modifying
 | 
			
		||||
	present bool     // c present on ch
 | 
			
		||||
	modes   uint     // channel user modes
 | 
			
		||||
 | 
			
		||||
	adding   bool // currently adding modes
 | 
			
		||||
	modeChar byte // currently processed mode char
 | 
			
		||||
 | 
			
		||||
	added   []byte  // added modes
 | 
			
		||||
	removed []byte  // removed modes
 | 
			
		||||
	output  *[]byte // "added" or "removed"
 | 
			
		||||
 | 
			
		||||
	addedParams   []string  // params for added modes
 | 
			
		||||
	removedParams []string  // params for removed modes
 | 
			
		||||
	outputParams  *[]string // "addedParams" or "removedParams"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) nextParam() string {
 | 
			
		||||
	if len(mp.params) == 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	param := mp.params[0]
 | 
			
		||||
	mp.params = mp.params[1:]
 | 
			
		||||
	return param
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) checkOperator() bool {
 | 
			
		||||
	if (mp.present && 0 != mp.modes&ircChanModeOperator) ||
 | 
			
		||||
		0 != mp.c.mode&ircUserModeOperator {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mp.c.sendReply(ERR_CHANOPRIVSNEEDED, mp.ch.name)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) doUser(mode uint) {
 | 
			
		||||
	target := mp.nextParam()
 | 
			
		||||
	if !mp.checkOperator() || target == "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if client := users[ircToCanon(target)]; client == nil {
 | 
			
		||||
		mp.c.sendReply(ERR_NOSUCHNICK, target)
 | 
			
		||||
	} else if modes, present := mp.ch.userModes[client]; !present {
 | 
			
		||||
		mp.c.sendReply(ERR_USERNOTINCHANNEL, target, mp.ch.name)
 | 
			
		||||
	} else if ircModifyMode(&modes, mode, mp.adding) {
 | 
			
		||||
		mp.ch.userModes[client] = modes
 | 
			
		||||
		*mp.output = append(*mp.output, mp.modeChar)
 | 
			
		||||
		*mp.outputParams = append(*mp.outputParams, client.nickname)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) doChan(mode uint) bool {
 | 
			
		||||
	if !mp.checkOperator() || !ircModifyMode(&mp.ch.modes, mode, mp.adding) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	*mp.output = append(*mp.output, mp.modeChar)
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) doChanRemove(modeChar byte, mode uint) {
 | 
			
		||||
	if mp.adding && ircModifyMode(&mp.ch.modes, mode, false) {
 | 
			
		||||
		mp.removed = append(mp.removed, modeChar)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) doList(list *[]string, listMsg, endMsg int) {
 | 
			
		||||
	target := mp.nextParam()
 | 
			
		||||
	if target == "" {
 | 
			
		||||
		if mp.adding {
 | 
			
		||||
			ircSendChannelList(mp.c, mp.ch.name, *list, listMsg, endMsg)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !mp.checkOperator() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mask := ircCheckExpandUserMask(target)
 | 
			
		||||
	if mask == "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var i int
 | 
			
		||||
	for i = 0; i < len(*list); i++ {
 | 
			
		||||
		if ircEqual((*list)[i], mask) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	found := i < len(*list)
 | 
			
		||||
	if found != mp.adding {
 | 
			
		||||
		if mp.adding {
 | 
			
		||||
			*list = append(*list, mask)
 | 
			
		||||
		} else {
 | 
			
		||||
			*list = append((*list)[:i], (*list)[i+1:]...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		*mp.output = append(*mp.output, mp.modeChar)
 | 
			
		||||
		*mp.outputParams = append(*mp.outputParams, mask)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) doKey() {
 | 
			
		||||
	target := mp.nextParam()
 | 
			
		||||
	if !mp.checkOperator() || target == "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !mp.adding {
 | 
			
		||||
		if mp.ch.key == "" || !ircEqual(target, mp.ch.key) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mp.removed = append(mp.removed, mp.modeChar)
 | 
			
		||||
		mp.removedParams = append(mp.removedParams, mp.ch.key)
 | 
			
		||||
		mp.ch.key = ""
 | 
			
		||||
	} else if !ircIsValidKey(target) {
 | 
			
		||||
		// TODO: We should notify the user somehow.
 | 
			
		||||
		return
 | 
			
		||||
	} else if mp.ch.key != "" {
 | 
			
		||||
		mp.c.sendReply(ERR_KEYSET, mp.ch.name)
 | 
			
		||||
	} else {
 | 
			
		||||
		mp.ch.key = target
 | 
			
		||||
		mp.added = append(mp.added, mp.modeChar)
 | 
			
		||||
		mp.addedParams = append(mp.addedParams, mp.ch.key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) doLimit() {
 | 
			
		||||
	if !mp.checkOperator() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !mp.adding {
 | 
			
		||||
		if mp.ch.userLimit == -1 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mp.ch.userLimit = -1
 | 
			
		||||
		mp.removed = append(mp.removed, mp.modeChar)
 | 
			
		||||
	} else if target := mp.nextParam(); target != "" {
 | 
			
		||||
		if x, err := strconv.ParseInt(target, 10, 32); err == nil && x > 0 {
 | 
			
		||||
			mp.ch.userLimit = int(x)
 | 
			
		||||
			mp.added = append(mp.added, mp.modeChar)
 | 
			
		||||
			mp.addedParams = append(mp.addedParams, target)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *modeProcessor) step(modeChar byte) bool {
 | 
			
		||||
	mp.modeChar = modeChar
 | 
			
		||||
	switch mp.modeChar {
 | 
			
		||||
	case '+':
 | 
			
		||||
		mp.adding = true
 | 
			
		||||
		mp.output = &mp.added
 | 
			
		||||
		mp.outputParams = &mp.addedParams
 | 
			
		||||
	case '-':
 | 
			
		||||
		mp.adding = false
 | 
			
		||||
		mp.output = &mp.removed
 | 
			
		||||
		mp.outputParams = &mp.removedParams
 | 
			
		||||
 | 
			
		||||
	case 'o':
 | 
			
		||||
		mp.doUser(ircChanModeOperator)
 | 
			
		||||
	case 'v':
 | 
			
		||||
		mp.doUser(ircChanModeVoice)
 | 
			
		||||
 | 
			
		||||
	case 'i':
 | 
			
		||||
		mp.doChan(ircChanModeInviteOnly)
 | 
			
		||||
	case 'm':
 | 
			
		||||
		mp.doChan(ircChanModeModerated)
 | 
			
		||||
	case 'n':
 | 
			
		||||
		mp.doChan(ircChanModeNoOutsideMsgs)
 | 
			
		||||
	case 'q':
 | 
			
		||||
		mp.doChan(ircChanModeQuiet)
 | 
			
		||||
	case 't':
 | 
			
		||||
		mp.doChan(ircChanModeProtectedTopic)
 | 
			
		||||
 | 
			
		||||
	case 'p':
 | 
			
		||||
		if mp.doChan(ircChanModePrivate) {
 | 
			
		||||
			mp.doChanRemove('s', ircChanModeSecret)
 | 
			
		||||
		}
 | 
			
		||||
	case 's':
 | 
			
		||||
		if mp.doChan(ircChanModeSecret) {
 | 
			
		||||
			mp.doChanRemove('p', ircChanModePrivate)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case 'b':
 | 
			
		||||
		mp.doList(&mp.ch.banList, RPL_BANLIST, RPL_ENDOFBANLIST)
 | 
			
		||||
	case 'e':
 | 
			
		||||
		mp.doList(&mp.ch.banList, RPL_EXCEPTLIST, RPL_ENDOFEXCEPTLIST)
 | 
			
		||||
	case 'I':
 | 
			
		||||
		mp.doList(&mp.ch.banList, RPL_INVITELIST, RPL_ENDOFINVITELIST)
 | 
			
		||||
 | 
			
		||||
	case 'k':
 | 
			
		||||
		mp.doKey()
 | 
			
		||||
	case 'l':
 | 
			
		||||
		mp.doLimit()
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		// It's not safe to continue, results could be undesired.
 | 
			
		||||
		mp.c.sendReply(ERR_UNKNOWNMODE, modeChar, mp.ch.name)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleChanModeChange(c *client, ch *channel, params []string) {
 | 
			
		||||
	modes, present := ch.userModes[c]
 | 
			
		||||
	mp := &modeProcessor{
 | 
			
		||||
		c:       c,
 | 
			
		||||
		ch:      ch,
 | 
			
		||||
		present: present,
 | 
			
		||||
		modes:   modes,
 | 
			
		||||
		params:  params,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
Outer:
 | 
			
		||||
	for {
 | 
			
		||||
		modeString := mp.nextParam()
 | 
			
		||||
		if modeString == "" {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mp.step('+')
 | 
			
		||||
		for _, modeChar := range []byte(modeString) {
 | 
			
		||||
			if !mp.step(modeChar) {
 | 
			
		||||
				break Outer
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: Limit to three changes with parameter per command.
 | 
			
		||||
	if len(mp.added) > 0 || len(mp.removed) > 0 {
 | 
			
		||||
		buf := []byte(fmt.Sprintf(":%s!%s@%s MODE %s ",
 | 
			
		||||
			mp.c.nickname, mp.c.username, mp.c.hostname, mp.ch.name))
 | 
			
		||||
		if len(mp.added) > 0 {
 | 
			
		||||
			buf = append(buf, '+')
 | 
			
		||||
			buf = append(buf, mp.added...)
 | 
			
		||||
		}
 | 
			
		||||
		if len(mp.removed) > 0 {
 | 
			
		||||
			buf = append(buf, '-')
 | 
			
		||||
			buf = append(buf, mp.removed...)
 | 
			
		||||
		}
 | 
			
		||||
		for _, param := range mp.addedParams {
 | 
			
		||||
			buf = append(buf, ' ')
 | 
			
		||||
			buf = append(buf, param...)
 | 
			
		||||
		}
 | 
			
		||||
		for _, param := range mp.removedParams {
 | 
			
		||||
			buf = append(buf, ' ')
 | 
			
		||||
			buf = append(buf, param...)
 | 
			
		||||
		}
 | 
			
		||||
		ircChannelMulticast(mp.ch, string(buf), nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
 | 
			
		||||
func ircHandleMODE(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
@@ -1259,17 +1672,32 @@ func ircHandleMODE(msg *message, c *client) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO
 | 
			
		||||
	target := msg.params[0]
 | 
			
		||||
	client := users[ircToCanon(target)]
 | 
			
		||||
	ch := users[ircToCanon(target)]
 | 
			
		||||
	ch := channels[ircToCanon(target)]
 | 
			
		||||
 | 
			
		||||
	if client != nil {
 | 
			
		||||
		// TODO
 | 
			
		||||
		if ircEqual(target, c.nickname) {
 | 
			
		||||
			c.sendReply(ERR_USERSDONTMATCH)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(msg.params) < 2 {
 | 
			
		||||
			c.sendReply(RPL_UMODEIS, c.getMode())
 | 
			
		||||
		} else {
 | 
			
		||||
			ircHandleUserModeChange(c, msg.params[1])
 | 
			
		||||
		}
 | 
			
		||||
	} else if ch != nil {
 | 
			
		||||
		// TODO
 | 
			
		||||
		if len(msg.params) < 2 {
 | 
			
		||||
			_, present := ch.userModes[c]
 | 
			
		||||
			c.sendReply(RPL_CHANNELMODEIS, target, ch.getMode(present))
 | 
			
		||||
			c.sendReply(RPL_CREATIONTIME,
 | 
			
		||||
				target, ch.created/int64(time.Second))
 | 
			
		||||
		} else {
 | 
			
		||||
			ircHandleChanModeChange(c, ch, msg.params[1:])
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHNICK, target)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1973,21 +2401,128 @@ func ircHandleADMIN(msg *message, c *client) {
 | 
			
		||||
	c.sendReply(ERR_NOADMININFO, serverName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: All the remaining command handlers.
 | 
			
		||||
func ircHandleStatsLinks(c *client, msg *message) {
 | 
			
		||||
	// There is only an "l" query in RFC 2812 but we cannot link,
 | 
			
		||||
	// so instead we provide the "L" query giving information for all users.
 | 
			
		||||
	filter := ""
 | 
			
		||||
	if len(msg.params) > 1 {
 | 
			
		||||
		filter = msg.params[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func ircHandleX(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
	for _, client := range users {
 | 
			
		||||
		if filter != "" && !ircEqual(client.nickname, filter) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		c.sendReply(RPL_STATSLINKINFO,
 | 
			
		||||
			client.address,    // linkname
 | 
			
		||||
			len(client.sendQ), // sendq
 | 
			
		||||
			client.nSentMessages, client.sentBytes/1024,
 | 
			
		||||
			client.nReceivedMessages, client.receivedBytes/1024,
 | 
			
		||||
			(time.Now().UnixNano()-client.opened)/int64(time.Second))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleStatsCommands(c *client) {
 | 
			
		||||
	for name, handler := range ircHandlers {
 | 
			
		||||
		if handler.nReceived > 0 {
 | 
			
		||||
			c.sendReply(RPL_STATSCOMMANDS, name,
 | 
			
		||||
				handler.nReceived, handler.bytesReceived, 0)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// We need to do it this way because of an initialization loop concerning
 | 
			
		||||
// ircHandlers. Workaround proposed by rsc in #1817.
 | 
			
		||||
var ircHandleStatsCommandsIndirect func(c *client)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	ircHandleStatsCommandsIndirect = ircHandleStatsCommands
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleStatsUptime(c *client) {
 | 
			
		||||
	uptime := (time.Now().UnixNano() - started) / int64(time.Second)
 | 
			
		||||
 | 
			
		||||
	days := uptime / 60 / 60 / 24
 | 
			
		||||
	hours := (uptime % (60 * 60 * 24)) / 60 / 60
 | 
			
		||||
	mins := (uptime % (60 * 60)) / 60
 | 
			
		||||
	secs := uptime % 60
 | 
			
		||||
 | 
			
		||||
	c.sendReply(RPL_STATSUPTIME, days, hours, mins, secs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleSTATS(msg *message, c *client) {
 | 
			
		||||
	var query byte
 | 
			
		||||
	if len(msg.params) > 0 && len(msg.params[0]) > 0 {
 | 
			
		||||
		query = msg.params[0][0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(msg.params) > 1 && !isThisMe(msg.params[1]) {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHSERVER, msg.params[0])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if 0 == c.mode&ircUserModeOperator {
 | 
			
		||||
		c.sendReply(ERR_NOPRIVILEGES)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch query {
 | 
			
		||||
	case 'L':
 | 
			
		||||
		ircHandleStatsLinks(c, msg)
 | 
			
		||||
	case 'm':
 | 
			
		||||
		ircHandleStatsCommandsIndirect(c)
 | 
			
		||||
	case 'u':
 | 
			
		||||
		ircHandleStatsUptime(c)
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_ENDOFSTATS, query)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleLINKS(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) > 1 && !isThisMe(msg.params[0]) {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mask := "*"
 | 
			
		||||
	if len(msg.params) > 0 {
 | 
			
		||||
		if len(msg.params) > 1 {
 | 
			
		||||
			mask = msg.params[1]
 | 
			
		||||
		} else {
 | 
			
		||||
			mask = msg.params[0]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ircFnmatch(mask, serverName) {
 | 
			
		||||
		c.sendReply(RPL_LINKS, mask, serverName,
 | 
			
		||||
			0 /* hop count */, "TODO server_info from configuration")
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_ENDOFLINKS, mask)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleKILL(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 2 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if 0 == c.mode&ircUserModeOperator {
 | 
			
		||||
		c.sendReply(ERR_NOPRIVILEGES)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := users[ircToCanon(msg.params[0])]
 | 
			
		||||
	if target == nil {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHNICK, msg.params[0])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.sendf(":%s!%s@%s KILL %s :%s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, target.nickname, msg.params[1])
 | 
			
		||||
	target.closeLink(fmt.Sprintf("Killed by %s: %s", c.nickname, msg.params[1]))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleDIE(msg *message, c *client) {
 | 
			
		||||
	if 0 == c.mode&ircUserModeOperator {
 | 
			
		||||
		c.sendReply(ERR_NOPRIVILEGES)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !quitting {
 | 
			
		||||
	} else if !quitting {
 | 
			
		||||
		initiateQuit()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -2015,6 +2550,8 @@ var ircHandlers = map[string]*ircCommand{
 | 
			
		||||
	"SUMMON":   {true, ircHandleSUMMON, 0, 0},
 | 
			
		||||
	"AWAY":     {true, ircHandleAWAY, 0, 0},
 | 
			
		||||
	"ADMIN":    {true, ircHandleADMIN, 0, 0},
 | 
			
		||||
	"STATS":    {true, ircHandleSTATS, 0, 0},
 | 
			
		||||
	"LINKS":    {true, ircHandleLINKS, 0, 0},
 | 
			
		||||
 | 
			
		||||
	"MODE":    {true, ircHandleMODE, 0, 0},
 | 
			
		||||
	"PRIVMSG": {true, ircHandlePRIVMSG, 0, 0},
 | 
			
		||||
@@ -2031,7 +2568,8 @@ var ircHandlers = map[string]*ircCommand{
 | 
			
		||||
	"WHOWAS":  {true, ircHandleWHOWAS, 0, 0},
 | 
			
		||||
	"ISON":    {true, ircHandleISON, 0, 0},
 | 
			
		||||
 | 
			
		||||
	"DIE": {true, ircHandleDIE, 0, 0},
 | 
			
		||||
	"KILL": {true, ircHandleKILL, 0, 0},
 | 
			
		||||
	"DIE":  {true, ircHandleDIE, 0, 0},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircProcessMessage(c *client, msg *message, raw string) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user