hid: port PRIVMSG, NOTICE, NAMES, WHO, WHOIS/WAS, TOPIC, SUMMON, USERS
This commit is contained in:
		
							parent
							
								
									208a8fcc7e
								
							
						
					
					
						commit
						3322fe2851
					
				
							
								
								
									
										520
									
								
								xS/main.go
									
									
									
									
									
								
							
							
						
						
									
										520
									
								
								xS/main.go
									
									
									
									
									
								
							| @ -201,7 +201,7 @@ func readConfigFile(name string, output interface{}) error { | |||||||
| // --- Rate limiter ------------------------------------------------------------ | // --- Rate limiter ------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| type floodDetector struct { | type floodDetector struct { | ||||||
| 	interval   uint    // interval for the limit | 	interval   uint    // interval for the limit in seconds | ||||||
| 	limit      uint    // maximum number of events allowed | 	limit      uint    // maximum number of events allowed | ||||||
| 	timestamps []int64 // timestamps of last events | 	timestamps []int64 // timestamps of last events | ||||||
| 	pos        uint    // index of the oldest event | 	pos        uint    // index of the oldest event | ||||||
| @ -217,7 +217,7 @@ func newFloodDetector(interval, limit uint) *floodDetector { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (fd *floodDetector) check() bool { | func (fd *floodDetector) check() bool { | ||||||
| 	now := time.Now().Unix() | 	now := time.Now().UnixNano() | ||||||
| 	fd.timestamps[fd.pos] = now | 	fd.timestamps[fd.pos] = now | ||||||
| 
 | 
 | ||||||
| 	fd.pos++ | 	fd.pos++ | ||||||
| @ -226,7 +226,7 @@ func (fd *floodDetector) check() bool { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var count uint | 	var count uint | ||||||
| 	begin := now - int64(fd.interval) | 	begin := now - int64(time.Second)*int64(fd.interval) | ||||||
| 	for _, ts := range fd.timestamps { | 	for _, ts := range fd.timestamps { | ||||||
| 		if ts >= begin { | 		if ts >= begin { | ||||||
| 			count++ | 			count++ | ||||||
| @ -471,12 +471,11 @@ func newWhowasInfo(c *client) *whowasInfo { | |||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||||||
| 
 | 
 | ||||||
| type ircCommand struct { | type ircCommand struct { | ||||||
| 	name                 string |  | ||||||
| 	requiresRegistration bool | 	requiresRegistration bool | ||||||
| 	handler              func(*message, *client) | 	handler              func(*message, *client) | ||||||
| 
 | 
 | ||||||
| 	nReceived     uint // number of commands received | 	nReceived     uint // number of commands received | ||||||
| 	bytesReceived uint // number of bytes received total | 	bytesReceived int  // number of bytes received total | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type preparedEvent struct { | type preparedEvent struct { | ||||||
| @ -563,39 +562,29 @@ func ircChannelDestroyIfEmpty(ch *channel) { | |||||||
| 	// TODO | 	// 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) { | func ircSendToRoommates(c *client, message string) { | ||||||
| 	// TODO | 	// TODO | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // --- Clients (continued) ----------------------------------------------------- | // --- Clients (continued) ----------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| func clientModeToString(m uint, mode *[]byte) { | func ircAppendClientModes(m uint, mode []byte) []byte { | ||||||
| 	if 0 != m&ircUserModeInvisible { | 	if 0 != m&ircUserModeInvisible { | ||||||
| 		*mode = append(*mode, 'i') | 		mode = append(mode, 'i') | ||||||
| 	} | 	} | ||||||
| 	if 0 != m&ircUserModeRxWallops { | 	if 0 != m&ircUserModeRxWallops { | ||||||
| 		*mode = append(*mode, 'w') | 		mode = append(mode, 'w') | ||||||
| 	} | 	} | ||||||
| 	if 0 != m&ircUserModeRestricted { | 	if 0 != m&ircUserModeRestricted { | ||||||
| 		*mode = append(*mode, 'r') | 		mode = append(mode, 'r') | ||||||
| 	} | 	} | ||||||
| 	if 0 != m&ircUserModeOperator { | 	if 0 != m&ircUserModeOperator { | ||||||
| 		*mode = append(*mode, 'o') | 		mode = append(mode, 'o') | ||||||
| 	} | 	} | ||||||
| 	if 0 != m&ircUserModeRxServerNotices { | 	if 0 != m&ircUserModeRxServerNotices { | ||||||
| 		*mode = append(*mode, 's') | 		mode = append(mode, 's') | ||||||
| 	} | 	} | ||||||
|  | 	return mode | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *client) getMode() string { | func (c *client) getMode() string { | ||||||
| @ -603,8 +592,7 @@ func (c *client) getMode() string { | |||||||
| 	if c.awayMessage != "" { | 	if c.awayMessage != "" { | ||||||
| 		mode = append(mode, 'a') | 		mode = append(mode, 'a') | ||||||
| 	} | 	} | ||||||
| 	clientModeToString(c.mode, &mode) | 	return string(ircAppendClientModes(c.mode, mode)) | ||||||
| 	return string(mode) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *client) send(line string) { | func (c *client) send(line string) { | ||||||
| @ -1215,15 +1203,15 @@ func ircHandleVERSION(msg *message, c *client) { | |||||||
| 	c.sendISUPPORT() | 	c.sendISUPPORT() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* |  | ||||||
| func ircChannelMulticast(ch *channel, msg string, except *client) { | func ircChannelMulticast(ch *channel, msg string, except *client) { | ||||||
| 	for c, m := range ch.userModes { | 	for c := range ch.userModes { | ||||||
| 		if c != except { | 		if c != except { | ||||||
| 			c.send(msg) | 			c.send(msg) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
| func ircModifyMode(mask *uint, mode uint, add bool) bool { | func ircModifyMode(mask *uint, mode uint, add bool) bool { | ||||||
| 	orig := *mask | 	orig := *mask | ||||||
| 	if add { | 	if add { | ||||||
| @ -1271,21 +1259,484 @@ func ircHandleUserMessage(msg *message, c *client, | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	target, text := msg.params[0], msg.params[1] | 	target, text := msg.params[0], msg.params[1] | ||||||
| 	if client, ok := users[ircToCanon(target)]; ok { | 	message := fmt.Sprintf(":%s!%s@%s %s %s :%s", | ||||||
| 		// TODO | 		c.nickname, c.username, c.hostname, command, target, text) | ||||||
| 		_ = client | 
 | ||||||
| 		_ = text | 	if client := users[ircToCanon(target)]; client != nil { | ||||||
| 	} else if ch, ok := channels[ircToCanon(target)]; ok { | 		client.send(message) | ||||||
| 		// TODO | 		if allowAwayReply && client.awayMessage != "" { | ||||||
| 		_ = ch | 			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 { | 	} else { | ||||||
| 		c.sendReply(ERR_NOSUCHNICK, target) | 		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. | // TODO: All the various real command handlers. | ||||||
| 
 | 
 | ||||||
| func ircHandleX(msg *message, c *client) { | 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:]) | 			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 { | 	if readErr != nil { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user