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