irc/commands.go

675 lines
20 KiB
Go

package irc
import (
"fmt"
"strconv"
"strings"
"github.com/sorcix/irc"
)
// CommandHandler allows objects implementing this interface to be registered to serve a particular IRC command
type CommandHandler interface {
ServeIRC(message *irc.Message, client *Client)
}
// CommandHandlerFunc is a wrapper to to use regular functions as a CommandHandler
type CommandHandlerFunc func(message *irc.Message, client *Client)
// ServeIRC services a given IRC message from the given client
func (f CommandHandlerFunc) ServeIRC(message *irc.Message, client *Client) {
f(message, client)
}
// PingHandler is a CommandHandler to respond to IRC PING commands from a client
// Implemented according to RFC 1459 Section 4.6.2 and RFC 2812 Section 3.7.2
func PingHandler(message *irc.Message, client *Client) {
client.Pong()
}
// PongHandler is a CommandHandler to respond to IRC PONG commands from a client
// Implemented according to RFC 1459 Section 4.6.3 and RFC 2812 Section 3.7.3
func PongHandler(message *irc.Message, client *Client) {
//client.Ping()
}
// QuitHandler is a CommandHandler to respond to IRC QUIT commands from a client
// Implemented according to RFC 1459 Section 4.1.6 and RFC 2812 Section 3.1.7
func QuitHandler(message *irc.Message, client *Client) {
var leavingMessage string
if len(message.Params) != 0 {
leavingMessage = message.Params[0]
} else if len(message.Trailing) != 0 {
leavingMessage = message.Trailing
} else {
leavingMessage = client.Nickname
}
for _, channel := range client.GetChannels() {
channel.Quit(client, leavingMessage)
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERROR, Trailing: "quit"}
client.Encode(&m)
client.Close()
}
// NickHandler is a CommandHandler to respond to IRC NICK commands from a client
// Implemented according to RFC 1459 Section 4.1.2 and RFC 2812 Section 3.1.2
func NickHandler(message *irc.Message, client *Client) {
var m irc.Message
name := client.Server.Config.Name
nickname := client.Nickname
if len(message.Params) == 0 {
m = irc.Message{Prefix: &irc.Prefix{Name: name}, Command: irc.ERR_NONICKNAMEGIVEN, Trailing: "No nickname given"}
client.Encode(&m)
return
}
newNickname := message.Params[0]
_, found := client.Server.GetClientByNick(newNickname)
switch {
case !client.authorized:
m = irc.Message{Prefix: &irc.Prefix{Name: name}, Command: irc.ERR_PASSWDMISMATCH, Params: []string{newNickname}, Trailing: "Password incorrect"}
case found: // nickname already in use
fmt.Println("Nickname already used")
m = irc.Message{Prefix: &irc.Prefix{Name: name}, Command: irc.ERR_NICKNAMEINUSE, Params: []string{newNickname}, Trailing: "Nickname is already in use"}
default:
if len(client.Nickname) == 0 && len(client.Username) != 0 { // Client is connected now, show MOTD ...
client.Nickname = newNickname
client.Server.AddClientNick(client)
client.Welcome()
} else { //change client name
client.Nickname = newNickname
client.Server.UpdateClientNick(client, nickname)
//fmt.Println("Updating client name")
}
}
if len(m.Command) != 0 {
client.Encode(&m)
}
}
// UserHandler is a CommandHandler to respond to IRC USER commands from a client
// Implemented according to RFC 1459 Section 4.1.3 and RFC 2812 Section 3.1.3
func UserHandler(message *irc.Message, client *Client) {
var m irc.Message
serverName := client.Server.Config.Name
//nickname := client.Nickname
if len(client.Username) != 0 { // Already registered
m = irc.Message{Prefix: &irc.Prefix{Name: serverName}, Command: irc.ERR_ALREADYREGISTRED, Trailing: "You may not reregister"}
client.Encode(&m)
return
}
if len(message.Params) != 3 {
m = irc.Message{Prefix: &irc.Prefix{Name: serverName}, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
name := message.Params[0]
username := message.Params[1]
hostname := message.Params[2]
realName := message.Trailing
client.Name = name
client.Username = username
client.Host = hostname
client.RealName = realName
if len(m.Command) == 0 && len(client.Nickname) != 0 { // Client has finished connecting
client.Welcome()
return
}
if len(m.Command) != 0 {
client.Encode(&m)
}
}
// JoinHandler is a CommandHandler to respond to IRC JOIN commands from a client
// Implemented according to RFC 1459 Section 4.2.1 and RFC 2812 Section 3.2.1
func JoinHandler(message *irc.Message, client *Client) {
channelNames := message.Params[0]
if channelNames == "0" { // Leave all channels
for _, channel := range client.GetChannels() {
channel.Part(client, "")
}
return
}
channelList := strings.Split(channelNames, ",")
keys := ""
if len(message.Params) >= 2 {
keys = message.Params[1]
}
keyList := strings.Split(keys, ",")
for i, cName := range channelList {
var key string
if len(keyList) > i {
key = keyList[i]
}
channel, ok := client.Server.GetChannel(cName)
if !ok { // Channel doesn't exist yet
channel = NewChannel(client.Server, client)
channel.Name = cName
//channel.Members[client.Nickname] = client.Nickname
channel.Key = key
client.Server.AddChannel(channel)
} else { //Channel already exists
}
//Notify channel members of new member
channel.Join(client, key)
}
}
// PartHandler is a CommandHandler to respond to IRC PART commands from a client
// Implemented according to RFC 1459 Section 4.2.2 and RFC 2812 Section 3.2.2
func PartHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
for _, cName := range message.Params {
channel, ok := client.Server.GetChannel(cName)
if !ok { // Channel doesn't exist yet
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Trailing: "You're not on that channel"}
client.Encode(&m)
continue
} else { //Channel already exists
}
channel.Part(client, message.Trailing)
}
}
// PrivMsgHandler is a CommandHandler to respond to IRC PRIVMSG commands from a client
// Implemented according to RFC 1459 Section 4.4.1 and RFC 2812 Section 3.3.1
func PrivMsgHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NORECIPIENT, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
return
}
if len(message.Params) > 1 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_TOOMANYTARGETS, Params: []string{client.Nickname}}
client.Encode(&m)
return
}
if len(message.Trailing) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTEXTTOSEND, Params: []string{client.Nickname}, Trailing: "No text to send"}
client.Encode(&m)
return
}
to := message.Params[0]
ch, ok := client.Server.GetChannel(to)
if ok { // message is to a channel
ch.Message(client, message.Trailing)
return
}
// message to a user?
cl, ok := client.Server.GetClientByNick(to)
if ok {
m := irc.Message{Prefix: client.Prefix, Command: irc.PRIVMSG, Params: []string{cl.Nickname}, Trailing: message.Trailing}
cl.Encode(&m)
if cl.HasMode(UserModeAway) {
m := irc.Message{Prefix: cl.Server.Prefix, Command: irc.RPL_AWAY, Params: []string{client.Nickname, cl.Nickname}, Trailing: cl.AwayMessage}
client.Encode(&m)
}
return
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHNICK, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
}
// NoticeHandler is a CommandHandler to respond to IRC NOTICE commands from a client
// Implemented according to RFC 1459 Section 4.4.2 and RFC 2812 Section 3.3.2
func NoticeHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NORECIPIENT, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
return
}
if len(message.Params) > 1 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_TOOMANYTARGETS, Params: []string{client.Nickname}}
client.Encode(&m)
return
}
if len(message.Trailing) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTEXTTOSEND, Params: []string{client.Nickname}, Trailing: "No text to send"}
client.Encode(&m)
return
}
to := message.Params[0]
ch, ok := client.Server.GetChannel(to)
if ok { // message is to a channel
ch.Notice(client, message.Trailing)
return
}
// message to a user?
cl, ok := client.Server.GetClientByNick(to)
if ok {
m := irc.Message{Prefix: client.Prefix, Command: irc.NOTICE, Params: []string{cl.Nickname}, Trailing: message.Trailing}
cl.Encode(&m)
return
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHNICK, Params: []string{client.Nickname}, Trailing: "No recipient given (PRIVMSG)"}
client.Encode(&m)
}
// WhoHandler is a CommandHandler to respond to IRC WHO commands from a client
// Implemented according to RFC 1459 Section 4.5.1 and RFC 2812 Section 3.6.1
func WhoHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
//return listing of all users
return
}
ch, ok := client.Server.GetChannel(message.Params[0])
if ok { //Channel exists
for clientName := range ch.members {
cl, found := client.Server.GetClientByNick(clientName)
if found {
msg := whoLine(cl, ch, client.Nickname)
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)}
client.Encode(&m)
}
}
} else {
// Not a channel, maybe a user
cl, ok := client.Server.GetClientByNick(message.Params[0])
if ok {
msg := whoLine(cl, nil, client.Nickname)
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_WHOREPLY, Params: strings.Fields(msg)}
client.Encode(&m)
}
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFWHO, Params: []string{client.Nickname, message.Params[0]}, Trailing: "End of WHO list"}
client.Encode(&m)
}
func whoLine(client *Client, channel *Channel, recipientClient string) string {
channelName := "*"
here := "H"
if client.UserModeSet.HasMode(UserModeAway) {
here = "G"
}
opStatus := ""
if client.HasMode(UserModeOperator) || client.HasMode(UserModeLocalOperator) {
opStatus += "*"
}
if channel != nil {
channelName = channel.Name
if channel.MemberHasMode(client, ChannelModeOperator) {
opStatus += "@"
}
}
hopCount := 0 //For now only local clients allowed - no federation
return fmt.Sprintf("%s %s %s %s %s %s %s%s :%d %s", recipientClient, channelName, client.Name, client.Host, client.Server.Config.Name, client.Nickname, here, opStatus, hopCount, client.RealName)
}
// TopicHandler is a CommandHandler to respond to IRC TOPIC commands from a client
// Implemented according to RFC 1459 Section 4.2.4 and RFC 2812 Section 3.2.4
func TopicHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
channelName := message.Params[0]
channel, ok := client.Server.GetChannel(channelName)
if !ok {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Params: []string{client.Nickname, channelName}, Trailing: "No such channel"}
client.Encode(&m)
return
}
m := ""
if len(message.Params) > 1 {
m = message.Params[1]
} else {
m = message.Trailing
}
channel.TopicCommand(client, m)
}
// AwayHandler is a CommandHandler to respond to IRC AWAY commands from a client
// Implemented according to RFC 1459 Section 5.1 and RFC 2812 Section 4.1
func AwayHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 && len(message.Trailing) == 0 {
client.AwayMessage = ""
client.RemoveMode(UserModeAway)
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UNAWAY, Params: []string{client.Nickname}, Trailing: "You are no longer marked as being away"}
client.Encode(&m)
return
}
if len(message.Trailing) > 0 {
client.AwayMessage = message.Trailing
} else {
client.AwayMessage = strings.Join(message.Params, " ")
}
client.AddMode(UserModeAway)
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_NOWAWAY, Params: []string{client.Nickname}, Trailing: "You have been marked as being away"}
client.Encode(&m)
return
}
// ModeHandler is a CommandHandler to respond to IRC MODE commands from a client
// Implemented according to RFC 1459 Section 4.2.3 and RFC 2812 Section 3.1.5 and RFC 2811
func ModeHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
id := message.Params[0]
_, ok := client.Server.GetChannel(id)
if ok {
ChannelModeHandler(message, client)
return
}
UserModeHandler(message, client)
}
// UserModeHandler is a specialized CommandHandler to respond to global or user IRC MODE commands from a client
// Implemented according to RFC 1459 Section 4.2.3.2 and RFC 2812 Section 3.1.5
func UserModeHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
username := message.Params[0]
if username != client.Nickname {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_USERSDONTMATCH, Params: []string{client.Nickname}, Trailing: "Cannot change mode for other users"}
client.Encode(&m)
return
}
if len(message.Params) == 1 { // just nickname is provided
// return current settings for this user
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{client.Nickname, client.UserModeSet.String()}}
client.Encode(&m)
return
}
for _, modeFlags := range message.Params[1:] {
modifier := ModeModifier(modeFlags[0])
switch modifier {
case ModeModifierAdd:
case ModeModifierRemove:
default:
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Params: []string{client.Nickname}, Trailing: "Unknown MODE flag"}
client.Encode(&m)
return
}
for _, modeFlag := range modeFlags[1:] {
mode := UserMode(modeFlag)
_, ok := UserModes[mode]
if !ok || mode == UserModeAway { // Away flag should only be set with AWAY command
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Params: []string{client.Nickname}, Trailing: "Unknown MODE flag"}
client.Encode(&m)
return
}
if modifier == ModeModifierAdd {
switch mode {
case UserModeOperator, UserModeLocalOperator: // Can't make oneself an operator
default:
client.AddMode(mode)
}
} else if modifier == ModeModifierRemove {
switch mode {
case UserModeRestricted: // Can't remove oneself from being restricted
default:
client.RemoveMode(mode)
}
}
}
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{client.Nickname, client.UserModeSet.String()}}
client.Encode(&m)
return
}
// ChannelModeHandler is a specialized CommandHandler to respond to channel IRC MODE commands from a client
// Implemented according to RFC 1459 Section 4.2.3.1 and RFC 2811
func ChannelModeHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
channelName := message.Params[0]
channel, ok := client.Server.GetChannel(channelName)
if !ok {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Trailing: "No such channel"}
client.Encode(&m)
return
}
if len(message.Params) == 1 { // just channel name is provided
// return current settings for this channel
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_CHANNELMODEIS, Params: []string{client.Nickname, channel.Name, channel.ChannelModeSet.String()}}
client.Encode(&m)
return
}
if !channel.MemberHasMode(client, ChannelModeOperator) { // Only channel operators can make these changes
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_CHANOPRIVSNEEDED, Params: []string{client.Nickname, channel.Name}, Trailing: "You're not channel operator"}
client.Encode(&m)
return
}
type fullFlag struct {
ModeModifier
ChannelMode
Param string
}
needsArgs := []fullFlag{}
changes := []fullFlag{}
argsCount := 0
for _, param := range message.Params[1:] {
if len(needsArgs) != 0 { // This param will be an arg
mode := needsArgs[0]
needsArgs = needsArgs[1:]
argsCount++
if argsCount > 3 { //Only allow 3 argument based flags per mode command
break
}
mode.Param = param
switch mode.ChannelMode {
case ChannelModeOperator, ChannelModeVoice:
n, ok := client.Server.GetClientByNick(param)
if ok {
found := channel.MemberHasMode(client, mode.ChannelMode)
if mode.ModeModifier == ModeModifierAdd {
channel.AddMemberMode(n, mode.ChannelMode)
if !found {
changes = append(changes, mode)
}
} else {
channel.RemoveMemberMode(n, mode.ChannelMode)
if found {
changes = append(changes, mode)
}
}
}
case ChannelModeLimit:
l, err := strconv.Atoi(param)
if err == nil {
channel.SetLimit(l)
changes = append(changes, mode)
}
case ChannelModeKey:
channel.SetKey(param)
changes = append(changes, mode)
case ChannelModeBan, ChannelModeExceptionMask, ChannelModeInvitationMask:
fillMask := func(mask string) string {
p := irc.ParsePrefix(param)
if len(p.Name) == 0 {
p.Name = "*"
}
if len(p.User) == 0 {
p.User = "*"
}
if len(p.Host) == 0 {
p.Host = "*"
}
return p.String()
}
mask := fillMask(param)
switch mode.ChannelMode {
case ChannelModeBan:
masks := channel.GetBanMasks()
_, ok := masks[mask]
if !ok {
channel.AddBanMask(mask)
changes = append(changes, mode)
}
case ChannelModeExceptionMask:
masks := channel.GetExceptionMasks()
_, ok := masks[mask]
if !ok {
channel.AddExceptionMask(mask)
changes = append(changes, mode)
}
case ChannelModeInvitationMask:
masks := channel.GetInvitationMasks()
_, ok := masks[mask]
if !ok {
channel.AddInvitationMask(mask)
changes = append(changes, mode)
}
}
}
} else { // This should be a mode flag or series of mode flags
modifier := ModeModifierAdd
for _, char := range param {
mod := ModeModifier(char)
switch mod { // Set if flag is adding a removing a mode
case ModeModifierAdd:
modifier = ModeModifierAdd
case ModeModifierRemove:
modifier = ModeModifierRemove
}
flag := ChannelMode(char)
switch flag {
case ChannelModeVoice, ChannelModeOperator, ChannelModeExceptionMask, ChannelModeInvitationMask, ChannelModeBan, ChannelModeLimit:
needsArgs = append(needsArgs, fullFlag{modifier, flag, ""})
case ChannelModeKey:
if modifier == ModeModifierAdd {
needsArgs = append(needsArgs, fullFlag{modifier, flag, ""})
}
}
}
}
}
changeString := ""
paramsChanged := []string{}
previousMode := ModeModifier(' ')
for _, change := range changes {
if len(change.Param) != 0 {
paramsChanged = append(paramsChanged, change.Param)
}
if change.ModeModifier != previousMode {
changeString += string(change.ModeModifier)
}
changeString += string(change.ChannelMode)
previousMode = change.ModeModifier
}
params := []string{channel.Name, changeString}
params = append(params, paramsChanged...)
m := irc.Message{Prefix: client.Prefix, Command: irc.MODE, Params: params}
// Notify channel members of channel changes
channel.SendMessage(&m)
return
}
// NamesHandler is a specialized CommandHandler to respond to channel IRC NAMES commands from a client
// Implemented according to RFC 1459 Section 4.2.5 and RFC 2812 Section 3.2.5
func NamesHandler(message *irc.Message, client *Client) {
if len(message.Params) == 0 { // Send NAMES response for all channels
for _, ch := range client.Server.channels {
ch.Names(client)
}
} else {
channelNames := strings.Split(message.Params[0], ",")
for _, channelName := range channelNames {
ch, ok := client.Server.GetChannel(channelName)
if ok {
ch.Names(client)
}
}
}
}