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…
Reference in New Issue