1074 lines
35 KiB
Go
1074 lines
35 KiB
Go
package irc
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"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()
|
|
}
|
|
|
|
// RegisteredHandler is a CommandHandler middleware to check that a user/client is properly registered
|
|
func RegisteredHandler(h CommandHandler) CommandHandler {
|
|
return CommandHandlerFunc(func(message *irc.Message, client *Client) {
|
|
if client.Registered {
|
|
h.ServeIRC(message, client)
|
|
} else {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTREGISTERED, Trailing: "You have not registered"}
|
|
|
|
client.Encode(&m)
|
|
}
|
|
})
|
|
}
|
|
|
|
// PassHandler is a CommandHandler to respond to IRC PASS commands from a client
|
|
// Implemented according to RFC 1459 Section 4.1.1 and RFC 2812 Section 3.1.1
|
|
func PassHandler(message *irc.Message, client *Client) {
|
|
if len(message.Params) == 0 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "No nickname given"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
if len(client.Nickname) != 0 || len(client.Username) != 0 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_ALREADYREGISTRED, Trailing: "Unauthorized command (already registered)"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
client.Authorized = message.Params[0] == client.Server.Config.Password
|
|
|
|
}
|
|
|
|
// 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
|
|
//nickname := client.Nickname
|
|
|
|
if len(message.Params) == 0 {
|
|
m = irc.Message{Prefix: client.Server.Prefix, 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: client.Server.Prefix, 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: client.Server.Prefix, 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 if len(client.Username) != 0 { //change client name
|
|
client.UpdateNick(newNickname)
|
|
//fmt.Println("Updating client name")
|
|
} else {
|
|
client.Nickname = newNickname
|
|
client.Server.UpdateClientNick(client, newNickname)
|
|
}
|
|
}
|
|
|
|
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
|
|
//nickname := client.Nickname
|
|
|
|
if len(client.Username) != 0 { // Already registered
|
|
m = irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_ALREADYREGISTRED, Trailing: "You may not reregister"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
if len(message.Params) != 3 {
|
|
m = irc.Message{Prefix: client.Server.Prefix, 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 || len(message.Params[0]) == 0 || message.Params[0][0] == '*' {
|
|
//return listing of all visible users - visible people and people in channels with this client
|
|
client.Who()
|
|
|
|
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 && !client.HasMode(UserModeInvisible) {
|
|
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 || channel.HasMode(ChannelModeSecret) {
|
|
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
|
|
modes := channel.ChannelModeSet.Copy()
|
|
if !channel.HasMember(client) { // only current members should see the channel key
|
|
modes.SetKey("")
|
|
}
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_CHANNELMODEIS, Params: []string{client.Nickname, channel.Name, modes.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
|
|
needsArgs = []fullFlag{}
|
|
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)
|
|
mode.Param = mask
|
|
changes = append(changes, mode)
|
|
}
|
|
|
|
case ChannelModeExceptionMask:
|
|
masks := channel.GetExceptionMasks()
|
|
_, ok := masks[mask]
|
|
if !ok {
|
|
channel.AddExceptionMask(mask)
|
|
mode.Param = mask
|
|
changes = append(changes, mode)
|
|
}
|
|
|
|
case ChannelModeInvitationMask:
|
|
masks := channel.GetInvitationMasks()
|
|
_, ok := masks[mask]
|
|
if !ok {
|
|
channel.AddInvitationMask(mask)
|
|
mode.Param = 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
|
|
continue
|
|
case ModeModifierRemove:
|
|
modifier = ModeModifierRemove
|
|
continue
|
|
|
|
}
|
|
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, ""})
|
|
}
|
|
case ChannelModeAnonymous, ChannelModeInviteOnly, ChannelModeModerated, ChannelModeNoOutsideMessages,
|
|
ChannelModePrivate, ChannelModeSecret, ChannelModeQuiet, ChannelModeReOp, ChannelModeTopic:
|
|
|
|
if flag == ChannelModeAnonymous {
|
|
switch channel.Name[0] {
|
|
case '#', '+': // # and + channels can't be anonymous
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UNKNOWNMODE, Params: []string{client.Nickname, string(flag)}, Trailing: "is unknown mode char to me for " + channel.Name}
|
|
client.Encode(&m)
|
|
continue
|
|
case '!': // ! Channels can only have anonymous flag set, not unset
|
|
if modifier == ModeModifierRemove {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UNKNOWNMODE, Params: []string{client.Nickname, string(flag)}, Trailing: "is unknown mode char to me for " + channel.Name}
|
|
client.Encode(&m)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
found := channel.HasMode(flag)
|
|
if modifier == ModeModifierAdd {
|
|
|
|
if !found {
|
|
if flag == ChannelModePrivate && channel.HasMode(ChannelModeSecret) {
|
|
// Secret and private can't both be set
|
|
continue
|
|
}
|
|
if flag == ChannelModeSecret && channel.HasMode(ChannelModePrivate) {
|
|
// Secret and private can't both be set
|
|
channel.RemoveMode(ChannelModePrivate)
|
|
}
|
|
channel.AddMode(flag)
|
|
changes = append(changes, fullFlag{modifier, flag, ""})
|
|
}
|
|
|
|
} else {
|
|
if found {
|
|
channel.RemoveMode(flag)
|
|
changes = append(changes, fullFlag{modifier, flag, ""})
|
|
}
|
|
}
|
|
default:
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UNKNOWNMODE, Params: []string{client.Nickname, string(flag)}, Trailing: "is unknown mode char to me for " + channel.Name}
|
|
client.Encode(&m)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if len(changes) == 0 { // No changes were made
|
|
|
|
if len(needsArgs) != 0 { // query instead of modification
|
|
arg := needsArgs[0] //just return information on just one flag
|
|
|
|
m := irc.Message{Prefix: client.Server.Prefix, Params: []string{client.Nickname}}
|
|
switch arg.ChannelMode {
|
|
case ChannelModeBan:
|
|
m.Command = irc.RPL_BANLIST
|
|
for mask := range channel.GetBanMasks() {
|
|
p := irc.ParsePrefix(mask)
|
|
fmt.Println("New BanMask: ", p.String())
|
|
m.Params = []string{client.Nickname, channel.Name, mask}
|
|
|
|
client.Encode(&m)
|
|
}
|
|
m.Params = []string{client.Nickname, channel.Name}
|
|
m.Trailing = "End of channel ban list"
|
|
m.Command = irc.RPL_ENDOFBANLIST
|
|
client.Encode(&m)
|
|
|
|
case ChannelModeExceptionMask:
|
|
m.Command = irc.RPL_EXCEPTLIST
|
|
|
|
for mask := range channel.GetExceptionMasks() {
|
|
m.Params = []string{client.Nickname, channel.Name, mask}
|
|
|
|
client.Encode(&m)
|
|
}
|
|
|
|
m.Params = []string{client.Nickname, channel.Name}
|
|
m.Trailing = "End of channel exception list"
|
|
m.Command = irc.RPL_ENDOFEXCEPTLIST
|
|
client.Encode(&m)
|
|
|
|
case ChannelModeInvitationMask:
|
|
m.Command = irc.RPL_INVITELIST
|
|
for mask := range channel.GetInvitationMasks() {
|
|
m.Params = []string{client.Nickname, channel.Name, mask}
|
|
|
|
client.Encode(&m)
|
|
}
|
|
|
|
m.Params = []string{client.Nickname, channel.Name}
|
|
m.Trailing = "End of channel invite list"
|
|
m.Command = irc.RPL_ENDOFINVITELIST
|
|
client.Encode(&m)
|
|
|
|
}
|
|
|
|
}
|
|
return
|
|
}
|
|
|
|
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
|
|
|
|
named := map[string]interface{}{}
|
|
for _, ch := range client.Server.channels {
|
|
n := ch.Names(client)
|
|
for _, k := range n {
|
|
named[k] = nil
|
|
}
|
|
}
|
|
count := 0
|
|
memberStr := ""
|
|
for n, cl := range client.Server.clientsByNick {
|
|
_, alreadyNamed := named[n]
|
|
if !alreadyNamed && !cl.HasMode(UserModeInvisible) { //don't name people that are already named or that shouldn't be named
|
|
count++
|
|
if cl != nil {
|
|
if cl.HasMode(UserModeOperator) || cl.HasMode(UserModeLocalOperator) {
|
|
memberStr += "@"
|
|
}
|
|
memberStr += cl.Nickname + " "
|
|
|
|
}
|
|
if count%20 == 0 { // String is long enough, send message
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_NAMREPLY, Params: []string{client.Nickname, "*", "*"}, Trailing: memberStr}
|
|
client.Encode(&m)
|
|
memberStr = ""
|
|
}
|
|
|
|
}
|
|
}
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_NAMREPLY, Params: []string{client.Nickname, "*", "*"}, Trailing: memberStr}
|
|
client.Encode(&m)
|
|
m = irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFNAMES, Params: []string{client.Nickname, "*"}, Trailing: "End of NAMES list"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
if len(message.Params) == 2 { //Client has provided target server for the request
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHSERVER, Params: []string{client.Nickname, message.Params[0]}, Trailing: "No such server"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
channelNames := strings.Split(message.Params[0], ",")
|
|
for _, channelName := range channelNames {
|
|
ch, ok := client.Server.GetChannel(channelName)
|
|
if ok {
|
|
ch.Names(client)
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFNAMES, Params: []string{client.Nickname, ch.Name}, Trailing: "End of NAMES list"}
|
|
client.Encode(&m)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// MOTDHandler is a specialized CommandHandler to respond to channel IRC MOTD commands from a client
|
|
// Implemented according to RFC 1459 Section 8.5 and RFC 2812 Section 3.4.1
|
|
func MOTDHandler(message *irc.Message, client *Client) {
|
|
client.MOTD()
|
|
}
|
|
|
|
// ListHandler is a specialized CommandHandler to respond to channel IRC LIST commands from a client
|
|
// Implemented according to RFC 1459 Section 4.2.6 and RFC 2812 Section 3.2.6
|
|
func ListHandler(message *irc.Message, client *Client) {
|
|
|
|
/* Deprecated in RFC 2812
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_LISTSTART, Params: []string{client.Nickname, "Channel :Users Name"}}
|
|
client.Encode(&m)
|
|
*/
|
|
|
|
if len(message.Params) == 0 || len(message.Params[0]) == 0 { // Send LIST response for all channels
|
|
for _, ch := range client.Server.channels {
|
|
m := ch.ListMessage(client)
|
|
if m != nil {
|
|
fmt.Println(m.String())
|
|
client.Encode(m)
|
|
}
|
|
|
|
}
|
|
} else {
|
|
channelNames := strings.Split(message.Params[0], ",")
|
|
for _, channelName := range channelNames {
|
|
ch, ok := client.Server.GetChannel(channelName)
|
|
if ok {
|
|
m := ch.ListMessage(client)
|
|
if m != nil {
|
|
fmt.Println(m.String())
|
|
client.Encode(m)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_LISTEND, Params: []string{client.Nickname}, Trailing: "End of LIST"}
|
|
client.Encode(&m)
|
|
}
|
|
|
|
// KickHandler is a specialized CommandHandler to respond to channel IRC KICK commands from a client
|
|
// Implemented according to RFC 1459 Section 4.2.8 and RFC 2812 Section 3.2.8
|
|
func KickHandler(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
|
|
}
|
|
channels := strings.Split(message.Params[0], ",")
|
|
nicks := strings.Split(message.Params[1], ",")
|
|
if len(channels) != 1 && len(channels) != len(nicks) {
|
|
//"For the message to be syntactically correct, there MUST be either one channel parameter and multiple user parameter, or as many channel parameters as there are user parameters."
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Trailing: "Not enough parameters"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
comment := message.Trailing
|
|
if len(message.Params) == 3 {
|
|
comment = message.Params[2]
|
|
}
|
|
if len(channels) == 1 {
|
|
ch, ok := client.Server.GetChannel(channels[0])
|
|
if ok {
|
|
ch.Kick(client, nicks, comment)
|
|
} else {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Params: []string{client.Nickname, channels[0]}, Trailing: "No such channel"}
|
|
client.Encode(&m)
|
|
}
|
|
return
|
|
}
|
|
for i, channel := range channels {
|
|
ch, ok := client.Server.GetChannel(channel)
|
|
if ok {
|
|
ch.Kick(client, []string{nicks[i]}, comment)
|
|
} else {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Params: []string{client.Nickname, channel}, Trailing: "No such channel"}
|
|
client.Encode(&m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TimeHandler is a specialized CommandHandler to respond to channel IRC TIME commands from a client
|
|
// Implemented according to RFC 1459 Section 4.3.4 and RFC 2812 Section 3.4.6
|
|
func TimeHandler(message *irc.Message, client *Client) {
|
|
if len(message.Params) != 0 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHSERVER, Params: []string{client.Nickname, message.Params[0]}, Trailing: "No such server"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_TIME, Params: []string{client.Nickname, client.Server.Config.Name}, Trailing: time.Now().Format(time.UnixDate)}
|
|
client.Encode(&m)
|
|
}
|
|
|
|
// VersionHandler is a specialized CommandHandler to respond to channel IRC VERSION commands from a client
|
|
// Implemented according to RFC 1459 Section 4.3.1 and RFC 2812 Section 3.4.3
|
|
func VersionHandler(message *irc.Message, client *Client) {
|
|
if len(message.Params) != 0 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHSERVER, Params: []string{client.Nickname, message.Params[0]}, Trailing: "No such server"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
p := fmt.Sprintf("%s.%s %s", client.Server.Config.Version, "", client.Server.Config.Name)
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_VERSION, Params: []string{client.Nickname, p}}
|
|
client.Encode(&m)
|
|
}
|
|
|
|
// LinksHandler is a specialized CommandHandler to respond to channel IRC LINKS commands from a client
|
|
// Implemented according to RFC 1459 Section 4.3.3 and RFC 2812 Section 3.4.5
|
|
func LinksHandler(message *irc.Message, client *Client) {
|
|
if len(message.Params) == 2 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHSERVER, Params: []string{client.Nickname, message.Params[0]}, Trailing: "No such server"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
mask := "*"
|
|
if len(message.Params) != 0 {
|
|
mask = message.Params[0]
|
|
}
|
|
regMask := strings.Replace(mask, "*", ".*", -1)
|
|
ok, err := regexp.MatchString(regMask, client.Server.Config.Name)
|
|
if ok && err == nil {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_LINKS, Params: []string{client.Nickname, mask, client.Server.Config.Name}, Trailing: "0"}
|
|
client.Encode(&m)
|
|
}
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ENDOFLINKS, Params: []string{client.Nickname, mask}, Trailing: "End of LINKS list"}
|
|
client.Encode(&m)
|
|
}
|
|
|
|
// InviteHandler is a specialized CommandHandler to respond to channel IRC INVITE commands from a client
|
|
// Implemented according to RFC 1459 Section 4.2.7 and RFC 2812 Section 3.2.7
|
|
func InviteHandler(message *irc.Message, client *Client) {
|
|
if len(message.Params) != 2 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
nick := message.Params[0]
|
|
channelName := message.Params[1]
|
|
cl, ok := client.Server.GetClientByNick(nick)
|
|
if !ok {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHNICK, Params: []string{client.Nickname, nick}, Trailing: "No such nick/channel"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
channel, ok := client.Server.GetChannel(channelName)
|
|
if !ok { //channel doesn't exist, send invite
|
|
SendInvite(client, cl, channel)
|
|
return
|
|
}
|
|
|
|
if !channel.HasMember(client) {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTONCHANNEL, Params: []string{client.Nickname, channelName}, Trailing: "You're not on that channel"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
if channel.HasMember(cl) {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_USERONCHANNEL, Params: []string{client.Nickname, nick, channelName}, Trailing: "is already on channel"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
|
|
if channel.HasMode(ChannelModeInviteOnly) {
|
|
if !channel.MemberHasMode(client, ChannelModeOperator) { // if invite-only, only ops can send invites
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_CHANOPRIVSNEEDED, Params: []string{client.Nickname, channelName}, Trailing: "You're not channel operator"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
channel.AddInvitationMask(cl.Prefix.String())
|
|
}
|
|
|
|
SendInvite(client, cl, channel)
|
|
|
|
}
|
|
|
|
// SendInvite handles sending the invite messages to both parties
|
|
func SendInvite(inviter *Client, invitee *Client, channel *Channel) {
|
|
m := irc.Message{Prefix: inviter.Server.Prefix, Command: irc.RPL_INVITING, Params: []string{inviter.Nickname, channel.Name, invitee.Nickname}}
|
|
inviter.Encode(&m)
|
|
m.Params[0] = invitee.Nickname
|
|
invitee.Encode(&m)
|
|
|
|
if invitee.HasMode(UserModeAway) {
|
|
m := irc.Message{Prefix: inviter.Server.Prefix, Command: irc.RPL_AWAY, Params: []string{inviter.Nickname, invitee.Nickname}, Trailing: invitee.AwayMessage}
|
|
inviter.Encode(&m)
|
|
}
|
|
return
|
|
}
|
|
|
|
// IsonHandler is a specialized CommandHandler to respond to channel IRC ISON commands from a client
|
|
// Implemented according to RFC 1459 Section 5.8 and RFC 2812 Section 4.9
|
|
func IsonHandler(message *irc.Message, client *Client) {
|
|
if len(message.Params) == 0 && len(message.Trailing) == 0 {
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
|
|
client.Encode(&m)
|
|
return
|
|
}
|
|
nicks := message.Params
|
|
if len(nicks) == 0 {
|
|
nicks = strings.Split(message.Trailing, " ")
|
|
}
|
|
found := []string{}
|
|
for _, nick := range nicks {
|
|
_, ok := client.Server.GetClientByNick(nick)
|
|
if ok {
|
|
found = append(found, nick)
|
|
}
|
|
}
|
|
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_ISON, Params: []string{client.Nickname}, Trailing: strings.Join(found, " ")}
|
|
client.Encode(&m)
|
|
|
|
}
|