hid: port PRIVMSG, NOTICE, NAMES, WHO, WHOIS/WAS, TOPIC, SUMMON, USERS
This commit is contained in:
		
							
								
								
									
										520
									
								
								hid/main.go
									
									
									
									
									
								
							
							
						
						
									
										520
									
								
								hid/main.go
									
									
									
									
									
								
							@@ -201,7 +201,7 @@ func readConfigFile(name string, output interface{}) error {
 | 
			
		||||
// --- Rate limiter ------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
type floodDetector struct {
 | 
			
		||||
	interval   uint    // interval for the limit
 | 
			
		||||
	interval   uint    // interval for the limit in seconds
 | 
			
		||||
	limit      uint    // maximum number of events allowed
 | 
			
		||||
	timestamps []int64 // timestamps of last events
 | 
			
		||||
	pos        uint    // index of the oldest event
 | 
			
		||||
@@ -217,7 +217,7 @@ func newFloodDetector(interval, limit uint) *floodDetector {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fd *floodDetector) check() bool {
 | 
			
		||||
	now := time.Now().Unix()
 | 
			
		||||
	now := time.Now().UnixNano()
 | 
			
		||||
	fd.timestamps[fd.pos] = now
 | 
			
		||||
 | 
			
		||||
	fd.pos++
 | 
			
		||||
@@ -226,7 +226,7 @@ func (fd *floodDetector) check() bool {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var count uint
 | 
			
		||||
	begin := now - int64(fd.interval)
 | 
			
		||||
	begin := now - int64(time.Second)*int64(fd.interval)
 | 
			
		||||
	for _, ts := range fd.timestamps {
 | 
			
		||||
		if ts >= begin {
 | 
			
		||||
			count++
 | 
			
		||||
@@ -471,12 +471,11 @@ func newWhowasInfo(c *client) *whowasInfo {
 | 
			
		||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
 | 
			
		||||
type ircCommand struct {
 | 
			
		||||
	name                 string
 | 
			
		||||
	requiresRegistration bool
 | 
			
		||||
	handler              func(*message, *client)
 | 
			
		||||
 | 
			
		||||
	nReceived     uint // number of commands received
 | 
			
		||||
	bytesReceived uint // number of bytes received total
 | 
			
		||||
	bytesReceived int  // number of bytes received total
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type preparedEvent struct {
 | 
			
		||||
@@ -563,39 +562,29 @@ func ircChannelDestroyIfEmpty(ch *channel) {
 | 
			
		||||
	// TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: ircSendToRoommates
 | 
			
		||||
// Broadcast to all /other/ clients (telnet-friendly, also in accordance to
 | 
			
		||||
// the plan of extending this to an IRCd).
 | 
			
		||||
func broadcast(line string, except *client) {
 | 
			
		||||
	for c := range clients {
 | 
			
		||||
		if c != except {
 | 
			
		||||
			c.send(line)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendToRoommates(c *client, message string) {
 | 
			
		||||
	// TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Clients (continued) -----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
func clientModeToString(m uint, mode *[]byte) {
 | 
			
		||||
func ircAppendClientModes(m uint, mode []byte) []byte {
 | 
			
		||||
	if 0 != m&ircUserModeInvisible {
 | 
			
		||||
		*mode = append(*mode, 'i')
 | 
			
		||||
		mode = append(mode, 'i')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != m&ircUserModeRxWallops {
 | 
			
		||||
		*mode = append(*mode, 'w')
 | 
			
		||||
		mode = append(mode, 'w')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != m&ircUserModeRestricted {
 | 
			
		||||
		*mode = append(*mode, 'r')
 | 
			
		||||
		mode = append(mode, 'r')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != m&ircUserModeOperator {
 | 
			
		||||
		*mode = append(*mode, 'o')
 | 
			
		||||
		mode = append(mode, 'o')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != m&ircUserModeRxServerNotices {
 | 
			
		||||
		*mode = append(*mode, 's')
 | 
			
		||||
		mode = append(mode, 's')
 | 
			
		||||
	}
 | 
			
		||||
	return mode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *client) getMode() string {
 | 
			
		||||
@@ -603,8 +592,7 @@ func (c *client) getMode() string {
 | 
			
		||||
	if c.awayMessage != "" {
 | 
			
		||||
		mode = append(mode, 'a')
 | 
			
		||||
	}
 | 
			
		||||
	clientModeToString(c.mode, &mode)
 | 
			
		||||
	return string(mode)
 | 
			
		||||
	return string(ircAppendClientModes(c.mode, mode))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *client) send(line string) {
 | 
			
		||||
@@ -1215,15 +1203,15 @@ func ircHandleVERSION(msg *message, c *client) {
 | 
			
		||||
	c.sendISUPPORT()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func ircChannelMulticast(ch *channel, msg string, except *client) {
 | 
			
		||||
	for c, m := range ch.userModes {
 | 
			
		||||
	for c := range ch.userModes {
 | 
			
		||||
		if c != except {
 | 
			
		||||
			c.send(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func ircModifyMode(mask *uint, mode uint, add bool) bool {
 | 
			
		||||
	orig := *mask
 | 
			
		||||
	if add {
 | 
			
		||||
@@ -1271,21 +1259,484 @@ func ircHandleUserMessage(msg *message, c *client,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target, text := msg.params[0], msg.params[1]
 | 
			
		||||
	if client, ok := users[ircToCanon(target)]; ok {
 | 
			
		||||
		// TODO
 | 
			
		||||
		_ = client
 | 
			
		||||
		_ = text
 | 
			
		||||
	} else if ch, ok := channels[ircToCanon(target)]; ok {
 | 
			
		||||
		// TODO
 | 
			
		||||
		_ = ch
 | 
			
		||||
	message := fmt.Sprintf(":%s!%s@%s %s %s :%s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, command, target, text)
 | 
			
		||||
 | 
			
		||||
	if client := users[ircToCanon(target)]; client != nil {
 | 
			
		||||
		client.send(message)
 | 
			
		||||
		if allowAwayReply && client.awayMessage != "" {
 | 
			
		||||
			c.sendReply(RPL_AWAY, target, client.awayMessage)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Acknowledging a message from the client to itself would be silly.
 | 
			
		||||
		if client != c && (0 != c.capsEnabled&ircCapEchoMessage) {
 | 
			
		||||
			c.send(message)
 | 
			
		||||
		}
 | 
			
		||||
	} else if ch := channels[ircToCanon(target)]; ch != nil {
 | 
			
		||||
		modes, present := ch.userModes[c]
 | 
			
		||||
 | 
			
		||||
		outsider := !present && 0 != ch.modes&ircChanModeNoOutsideMsgs
 | 
			
		||||
		moderated := 0 != ch.modes&ircChanModeModerated &&
 | 
			
		||||
			0 == modes&(ircChanModeVoice|ircChanModeOperator)
 | 
			
		||||
		banned := c.inMaskList(ch.banList) && !c.inMaskList(ch.exceptionList)
 | 
			
		||||
 | 
			
		||||
		if outsider || moderated || banned {
 | 
			
		||||
			c.sendReply(ERR_CANNOTSENDTOCHAN, target)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		except := c
 | 
			
		||||
		if 0 != c.capsEnabled&ircCapEchoMessage {
 | 
			
		||||
			except = nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ircChannelMulticast(ch, message, except)
 | 
			
		||||
	} else {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHNICK, target)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandlePRIVMSG(msg *message, c *client) {
 | 
			
		||||
	ircHandleUserMessage(msg, c, "PRIVMSG", true /* allowAwayReply */)
 | 
			
		||||
	// Let's not care too much about success or failure.
 | 
			
		||||
	c.lastActive = time.Now().UnixNano()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleNOTICE(msg *message, c *client) {
 | 
			
		||||
	ircHandleUserMessage(msg, c, "NOTICE", false /* allowAwayReply */)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleLIST(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) > 1 && !isThisMe(msg.params[1]) {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHSERVER, msg.params[1])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// XXX: Maybe we should skip ircUserModeInvisible from user counts.
 | 
			
		||||
	if len(msg.params) == 0 {
 | 
			
		||||
		for _, ch := range channels {
 | 
			
		||||
			if _, present := ch.userModes[c]; present ||
 | 
			
		||||
				0 == ch.modes&(ircChanModePrivate|ircChanModeSecret) {
 | 
			
		||||
				c.sendReply(RPL_LIST, ch.name, len(ch.userModes), ch.topic)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for _, target := range splitString(msg.params[0], ",", true) {
 | 
			
		||||
			if ch := channels[ircToCanon(target)]; ch != nil &&
 | 
			
		||||
				0 == ch.modes&ircChanModeSecret {
 | 
			
		||||
				c.sendReply(RPL_LIST, ch.name, len(ch.userModes), ch.topic)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_LISTEND)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircAppendPrefixes(c *client, modes uint, buf []byte) []byte {
 | 
			
		||||
	var all []byte
 | 
			
		||||
	if 0 != modes&ircChanModeOperator {
 | 
			
		||||
		all = append(all, '@')
 | 
			
		||||
	}
 | 
			
		||||
	if 0 != modes&ircChanModeVoice {
 | 
			
		||||
		all = append(all, '+')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(all) > 0 {
 | 
			
		||||
		if 0 != c.capsEnabled&ircCapMultiPrefix {
 | 
			
		||||
			buf = append(buf, all...)
 | 
			
		||||
		} else {
 | 
			
		||||
			buf = append(buf, all[0])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return buf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircMakeRPLNAMREPLYItem(c, target *client, modes uint) string {
 | 
			
		||||
	result := string(ircAppendPrefixes(c, modes, nil)) + target.nickname
 | 
			
		||||
	if 0 != c.capsEnabled&ircCapUserhostInNames {
 | 
			
		||||
		result += fmt.Sprintf("!%s@%s", target.username, target.hostname)
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Consider using *client instead of string as the map key.
 | 
			
		||||
func ircSendRPLNAMREPLY(c *client, ch *channel, usedNicks map[string]bool) {
 | 
			
		||||
	kind := '='
 | 
			
		||||
	if 0 != ch.modes&ircChanModeSecret {
 | 
			
		||||
		kind = '@'
 | 
			
		||||
	} else if 0 != ch.modes&ircChanModePrivate {
 | 
			
		||||
		kind = '*'
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, present := ch.userModes[c]
 | 
			
		||||
 | 
			
		||||
	var nicks []string
 | 
			
		||||
	for client, modes := range ch.userModes {
 | 
			
		||||
		if !present && 0 != client.mode&ircUserModeInvisible {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if usedNicks != nil {
 | 
			
		||||
			usedNicks[ircToCanon(client.nickname)] = true
 | 
			
		||||
		}
 | 
			
		||||
		nicks = append(nicks, ircMakeRPLNAMREPLYItem(c, client, modes))
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReplyVector(RPL_NAMREPLY, nicks, kind, ch.name, "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendDisassociatedNames(c *client, usedNicks map[string]bool) {
 | 
			
		||||
	var nicks []string
 | 
			
		||||
	for canonNickname, client := range users {
 | 
			
		||||
		if 0 == client.mode&ircUserModeInvisible && !usedNicks[canonNickname] {
 | 
			
		||||
			nicks = append(nicks, ircMakeRPLNAMREPLYItem(c, client, 0))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(nicks) > 0 {
 | 
			
		||||
		c.sendReplyVector(RPL_NAMREPLY, nicks, '*', "*", "")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleNAMES(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) > 1 && !isThisMe(msg.params[1]) {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHSERVER, msg.params[1])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(msg.params) == 0 {
 | 
			
		||||
		usedNicks := make(map[string]bool)
 | 
			
		||||
 | 
			
		||||
		for _, ch := range channels {
 | 
			
		||||
			if _, present := ch.userModes[c]; present ||
 | 
			
		||||
				0 == ch.modes&(ircChanModePrivate|ircChanModeSecret) {
 | 
			
		||||
				ircSendRPLNAMREPLY(c, ch, usedNicks)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Also send all visible users we haven't listed yet.
 | 
			
		||||
		ircSendDisassociatedNames(c, usedNicks)
 | 
			
		||||
		c.sendReply(RPL_ENDOFNAMES, "*")
 | 
			
		||||
	} else {
 | 
			
		||||
		for _, target := range splitString(msg.params[0], ",", true) {
 | 
			
		||||
			if ch := channels[ircToCanon(target)]; ch == nil {
 | 
			
		||||
			} else if _, present := ch.userModes[c]; present ||
 | 
			
		||||
				0 == ch.modes&ircChanModeSecret {
 | 
			
		||||
				ircSendRPLNAMREPLY(c, ch, nil)
 | 
			
		||||
				c.sendReply(RPL_ENDOFNAMES, target)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendRPLWHOREPLY(c *client, ch *channel, target *client) {
 | 
			
		||||
	var chars []byte
 | 
			
		||||
	if target.awayMessage != "" {
 | 
			
		||||
		chars = append(chars, 'G')
 | 
			
		||||
	} else {
 | 
			
		||||
		chars = append(chars, 'H')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 != target.mode&ircUserModeOperator {
 | 
			
		||||
		chars = append(chars, '*')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	channelName := "*"
 | 
			
		||||
	if ch != nil {
 | 
			
		||||
		channelName = ch.name
 | 
			
		||||
		if modes, present := ch.userModes[target]; present {
 | 
			
		||||
			chars = ircAppendPrefixes(c, modes, chars)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.sendReply(RPL_WHOREPLY, channelName,
 | 
			
		||||
		target.username, target.hostname, serverName,
 | 
			
		||||
		target.nickname, string(chars), 0 /* hop count */, target.realname)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircMatchSendRPLWHOREPLY(c, target *client, mask string) {
 | 
			
		||||
	isRoommate := false
 | 
			
		||||
	for _, ch := range channels {
 | 
			
		||||
		_, presentClient := ch.userModes[c]
 | 
			
		||||
		_, presentTarget := ch.userModes[target]
 | 
			
		||||
		if presentClient && presentTarget {
 | 
			
		||||
			isRoommate = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !isRoommate && 0 != target.mode&ircUserModeInvisible {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ircFnmatch(mask, target.hostname) &&
 | 
			
		||||
		!ircFnmatch(mask, target.nickname) &&
 | 
			
		||||
		!ircFnmatch(mask, target.realname) &&
 | 
			
		||||
		!ircFnmatch(mask, serverName) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Try to find a channel they're on that's visible to us.
 | 
			
		||||
	var userCh *channel
 | 
			
		||||
	for _, ch := range channels {
 | 
			
		||||
		_, presentClient := ch.userModes[c]
 | 
			
		||||
		_, presentTarget := ch.userModes[target]
 | 
			
		||||
		if presentTarget && (presentClient ||
 | 
			
		||||
			0 == ch.modes&(ircChanModePrivate|ircChanModeSecret)) {
 | 
			
		||||
			userCh = ch
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ircSendRPLWHOREPLY(c, userCh, target)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleWHO(msg *message, c *client) {
 | 
			
		||||
	onlyOps := len(msg.params) > 1 && msg.params[1] == "o"
 | 
			
		||||
 | 
			
		||||
	shownMask, usedMask := "*", "*"
 | 
			
		||||
	if len(msg.params) > 0 {
 | 
			
		||||
		shownMask = msg.params[0]
 | 
			
		||||
		if shownMask != "0" {
 | 
			
		||||
			usedMask = shownMask
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ch := channels[ircToCanon(usedMask)]; ch != nil {
 | 
			
		||||
		_, present := ch.userModes[c]
 | 
			
		||||
		if present || 0 == ch.modes&ircChanModeSecret {
 | 
			
		||||
			for client := range ch.userModes {
 | 
			
		||||
				if (present || 0 == client.mode&ircUserModeInvisible) &&
 | 
			
		||||
					(!onlyOps || 0 != client.mode&ircUserModeOperator) {
 | 
			
		||||
					ircSendRPLWHOREPLY(c, ch, client)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for _, client := range users {
 | 
			
		||||
			if !onlyOps || 0 != client.mode&ircUserModeOperator {
 | 
			
		||||
				ircMatchSendRPLWHOREPLY(c, client, usedMask)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_ENDOFWHO, shownMask)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendWHOISReply(c, target *client) {
 | 
			
		||||
	nick := target.nickname
 | 
			
		||||
	c.sendReply(RPL_WHOISUSER, nick,
 | 
			
		||||
		target.username, target.hostname, target.realname)
 | 
			
		||||
	c.sendReply(RPL_WHOISSERVER, nick,
 | 
			
		||||
		serverName, "TODO server_info from configuration")
 | 
			
		||||
	if 0 != target.mode&ircUserModeOperator {
 | 
			
		||||
		c.sendReply(RPL_WHOISOPERATOR, nick)
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReply(RPL_WHOISIDLE, nick,
 | 
			
		||||
		(time.Now().UnixNano()-target.lastActive)/int64(time.Second))
 | 
			
		||||
	if target.awayMessage != "" {
 | 
			
		||||
		c.sendReply(RPL_AWAY, nick, target.awayMessage)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var chans []string
 | 
			
		||||
	for _, ch := range channels {
 | 
			
		||||
		_, presentClient := ch.userModes[c]
 | 
			
		||||
		modes, presentTarget := ch.userModes[target]
 | 
			
		||||
		if presentTarget && (presentClient ||
 | 
			
		||||
			0 == ch.modes&(ircChanModePrivate|ircChanModeSecret)) {
 | 
			
		||||
			// TODO: Deduplicate, ircAppendPrefixes just also cuts prefixes.
 | 
			
		||||
			var all []byte
 | 
			
		||||
			if 0 != modes&ircChanModeOperator {
 | 
			
		||||
				all = append(all, '@')
 | 
			
		||||
			}
 | 
			
		||||
			if 0 != modes&ircChanModeVoice {
 | 
			
		||||
				all = append(all, '+')
 | 
			
		||||
			}
 | 
			
		||||
			chans = append(chans, string(all)+ch.name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	c.sendReplyVector(RPL_WHOISCHANNELS, chans, nick, "")
 | 
			
		||||
	c.sendReply(RPL_ENDOFWHOIS, nick)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleWHOIS(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(msg.params) > 1 && !isThisMe(msg.params[0]) {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHSERVER, msg.params[0])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	masksStr := msg.params[0]
 | 
			
		||||
	if len(msg.params) > 1 {
 | 
			
		||||
		masksStr = msg.params[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, mask := range splitString(masksStr, ",", true /* ignoreEmpty */) {
 | 
			
		||||
		if strings.IndexAny(mask, "*?") < 0 {
 | 
			
		||||
			if target := users[ircToCanon(mask)]; target == nil {
 | 
			
		||||
				c.sendReply(ERR_NOSUCHNICK, mask)
 | 
			
		||||
			} else {
 | 
			
		||||
				ircSendWHOISReply(c, target)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, target := range users {
 | 
			
		||||
				if ircFnmatch(mask, target.nickname) {
 | 
			
		||||
					ircSendWHOISReply(c, target)
 | 
			
		||||
					found = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				c.sendReply(ERR_NOSUCHNICK, mask)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleWHOWAS(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(msg.params) > 2 && !isThisMe(msg.params[2]) {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHSERVER, msg.params[2])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// The "count" parameter is ignored, we only store one entry for a nick.
 | 
			
		||||
 | 
			
		||||
	for _, nick := range splitString(msg.params[0], ",", true) {
 | 
			
		||||
		if info := whowas[ircToCanon(nick)]; info == nil {
 | 
			
		||||
			c.sendReply(ERR_WASNOSUCHNICK, nick)
 | 
			
		||||
		} else {
 | 
			
		||||
			c.sendReply(RPL_WHOWASUSER, nick,
 | 
			
		||||
				info.username, info.hostname, info.realname)
 | 
			
		||||
			c.sendReply(RPL_WHOISSERVER, nick,
 | 
			
		||||
				serverName, "TODO server_info from configuration")
 | 
			
		||||
		}
 | 
			
		||||
		c.sendReply(RPL_ENDOFWHOWAS, nick)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircSendRPLTOPIC(c *client, ch *channel) {
 | 
			
		||||
	if ch.topic == "" {
 | 
			
		||||
		c.sendReply(RPL_NOTOPIC, ch.name)
 | 
			
		||||
	} else {
 | 
			
		||||
		c.sendReply(RPL_TOPIC, ch.name, ch.topic)
 | 
			
		||||
		c.sendReply(RPL_TOPICWHOTIME,
 | 
			
		||||
			ch.name, ch.topicWho, ch.topicTime/int64(time.Second))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleTOPIC(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := msg.params[0]
 | 
			
		||||
	ch := channels[ircToCanon(target)]
 | 
			
		||||
	if ch == nil {
 | 
			
		||||
		c.sendReply(ERR_NOSUCHCHANNEL, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(msg.params) < 2 {
 | 
			
		||||
		ircSendRPLTOPIC(c, ch)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	modes, present := ch.userModes[c]
 | 
			
		||||
	if !present {
 | 
			
		||||
		c.sendReply(ERR_NOTONCHANNEL, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 != ch.modes&ircChanModeProtectedTopic &&
 | 
			
		||||
		0 == modes&ircChanModeOperator {
 | 
			
		||||
		c.sendReply(ERR_CHANOPRIVSNEEDED, target)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ch.topic = msg.params[1]
 | 
			
		||||
	ch.topicWho = fmt.Sprintf("%s@%s@%s", c.nickname, c.username, c.hostname)
 | 
			
		||||
	ch.topicTime = time.Now().UnixNano()
 | 
			
		||||
 | 
			
		||||
	message := fmt.Sprintf(":%s!%s@%s TOPIC %s :%s",
 | 
			
		||||
		c.nickname, c.username, c.hostname, target, ch.topic)
 | 
			
		||||
	ircChannelMulticast(ch, message, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: All the various real command handlers.
 | 
			
		||||
 | 
			
		||||
func ircHandleX(msg *message, c *client) {
 | 
			
		||||
	if len(msg.params) < 1 {
 | 
			
		||||
		c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleSUMMON(msg *message, c *client) {
 | 
			
		||||
	c.sendReply(ERR_SUMMONDISABLED)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircHandleUSERS(msg *message, c *client) {
 | 
			
		||||
	c.sendReply(ERR_USERSDISABLED)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// TODO: Add an index for IRC_ERR_NOSUCHSERVER validation?
 | 
			
		||||
// TODO: Add a minimal parameter count?
 | 
			
		||||
// TODO: Add a field for oper-only commands?
 | 
			
		||||
var ircHandlers = map[string]*ircCommand{
 | 
			
		||||
	"CAP":  {false, ircHandleCAP, 0, 0},
 | 
			
		||||
	"PASS": {false, ircHandlePASS, 0, 0},
 | 
			
		||||
	"NICK": {false, ircHandleNICK, 0, 0},
 | 
			
		||||
	"USER": {false, ircHandleUSER, 0, 0},
 | 
			
		||||
 | 
			
		||||
	"USERHOST": {true, ircHandleUSERHOST, 0, 0},
 | 
			
		||||
	"LUSERS":   {true, ircHandleLUSERS, 0, 0},
 | 
			
		||||
	"MOTD":     {true, ircHandleMOTD, 0, 0},
 | 
			
		||||
	"PING":     {true, ircHandlePING, 0, 0},
 | 
			
		||||
	"PONG":     {false, ircHandlePONG, 0, 0},
 | 
			
		||||
	"QUIT":     {false, ircHandleQUIT, 0, 0},
 | 
			
		||||
	"TIME":     {true, ircHandleTIME, 0, 0},
 | 
			
		||||
	"VERSION":  {true, ircHandleVERSION, 0, 0},
 | 
			
		||||
	"USERS":    {true, ircHandleUSERS, 0, 0},
 | 
			
		||||
	"SUMMON":   {true, ircHandleSUMMON, 0, 0},
 | 
			
		||||
 | 
			
		||||
	"MODE":    {true, ircHandleMODE, 0, 0},
 | 
			
		||||
	"PRIVMSG": {true, ircHandlePRIVMSG, 0, 0},
 | 
			
		||||
	"NOTICE":  {true, ircHandleNOTICE, 0, 0},
 | 
			
		||||
	"TOPIC":   {true, ircHandleTOPIC, 0, 0},
 | 
			
		||||
	"LIST":    {true, ircHandleLIST, 0, 0},
 | 
			
		||||
	"NAMES":   {true, ircHandleNAMES, 0, 0},
 | 
			
		||||
	"WHO":     {true, ircHandleWHO, 0, 0},
 | 
			
		||||
	"WHOIS":   {true, ircHandleWHOIS, 0, 0},
 | 
			
		||||
	"WHOWAS":  {true, ircHandleWHOWAS, 0, 0},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ircProcessMessage(c *client, msg *message, raw string) {
 | 
			
		||||
	if c.closing {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.nReceivedMessages++
 | 
			
		||||
	c.receivedBytes += len(raw) + 2
 | 
			
		||||
 | 
			
		||||
	if !c.antiflood.check() {
 | 
			
		||||
		c.closeLink("Excess flood")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cmd, ok := ircHandlers[ircToCanon(msg.command)]; !ok {
 | 
			
		||||
		c.sendReply(ERR_UNKNOWNCOMMAND, msg.command)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd.nReceived++
 | 
			
		||||
		cmd.bytesReceived += len(raw) + 2
 | 
			
		||||
 | 
			
		||||
		if cmd.requiresRegistration && !c.registered {
 | 
			
		||||
			c.sendReply(ERR_NOTREGISTERED)
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd.handler(msg, c)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- ? -----------------------------------------------------------------------
 | 
			
		||||
@@ -1338,7 +1789,8 @@ func (c *client) onRead(data []byte, readErr error) {
 | 
			
		||||
			msg.params = append(msg.params, x[1:])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		broadcast(line, c)
 | 
			
		||||
		// XXX: And since it accepts LF, we miscalculate receivedBytes within.
 | 
			
		||||
		ircProcessMessage(c, &msg, line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if readErr != nil {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user