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 ------------------------------------------------------------
 | 
					// --- 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 {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user