Added proper parsing of channel modes.

Implemented parsing and storage of Channel Mode flags. Not all flags are currently being utilized.
This commit is contained in:
Justin 2015-08-07 17:37:41 -07:00
parent a6e09ed19f
commit 2ef0464d1d
6 changed files with 338 additions and 92 deletions

View File

@ -36,9 +36,9 @@ func (c *Channel) Join(client *Client, key string) {
if c.HasMember(client) { // client is already in this channel
return
}
if len(c.Key) != 0 { //if key is required, verify that client provided matching key
if c.HasMode(ChannelModeKey) { //if key is required, verify that client provided matching key
if c.Key != key {
m := irc.Message{Command: irc.ERR_BADCHANNELKEY}
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.ERR_BADCHANNELKEY, Params: []string{client.Nickname, c.Name}, Trailing: "Cannot join channel (+k)"}
err := client.Encode(&m)
if err != nil {
println(err.Error())
@ -46,6 +46,11 @@ func (c *Channel) Join(client *Client, key string) {
return
}
}
if c.HasMode(ChannelModeLimit) && c.GetMemberCount() >= c.GetLimit() { // Limit flag is set and limit is met
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.ERR_CHANNELISFULL, Params: []string{client.Nickname, c.Name}, Trailing: "Cannot join channel (+l)"}
client.Encode(&m)
}
creator := c.GetMemberCount() == 0
c.AddMember(client)
@ -55,6 +60,10 @@ func (c *Channel) Join(client *Client, key string) {
// Creator should be a channel operator - Maybe check if it is a "safe" channel
c.AddMemberMode(client, ChannelModeOperator)
if len(key) != 0 {
c.SetKey(key)
}
}
var m irc.Message
@ -66,6 +75,37 @@ func (c *Channel) Join(client *Client, key string) {
}
//Notify existing members that new member is joining
m = irc.Message{Prefix: client.Prefix, Command: irc.JOIN, Params: []string{c.Name}}
c.SendMessage(&m)
c.Names(client)
}
// SetKey sets the channel key
func (c *Channel) SetKey(key string) {
c.AddModeWithValue(ChannelModeKey, key)
}
// GetKey gets the key for the channel
func (c *Channel) GetKey() string {
val := c.GetMode(ChannelModeKey)
if val == nil {
return ""
}
return val.(string)
}
// Names responds to to IRC NAMES command for the channel
func (c *Channel) Names(client *Client) {
if c.HasMode(ChannelModeSecret) && !c.HasMember(client) { // If channel is secret and client isn't a member, don't reveal it
return
}
if c.HasMode(ChannelModePrivate) && !c.HasMember(client) { // If channel is private and client isn't a member, don't reply
return
}
allMembers := make([]string, len(c.members))
i := 0
@ -73,36 +113,46 @@ func (c *Channel) Join(client *Client, key string) {
allMembers[i] = member
i++
}
// send list of users in channel
//channelPrefix := ""
// RFC 2812 defines other channel prefixes
channelPrefix := "="
if c.HasMode(ChannelModeSecret) {
channelPrefix = "@"
}
if c.HasMode(ChannelModePrivate) {
channelPrefix = "*"
}
for i := 0; i < (len(c.members)/20)+1; i++ {
memberStr := "= " + c.Name + " :"
memberStr := ""
end := (i + 1) * 20
if end > len(c.members) {
end = len(c.members)
}
m := irc.Message{Prefix: client.Prefix, Command: irc.JOIN, Params: []string{c.Name}}
for _, member := range allMembers[i*20 : end] {
mClient, _ := client.Server.GetClientByNick(member)
if mClient != nil {
if c.MemberHasMode(mClient, ChannelModeOperator) {
memberStr += "@"
} else if c.MemberHasMode(mClient, ChannelModeVoice) {
memberStr += "+"
}
memberStr += mClient.Nickname + " "
mClient.Encode(&m)
}
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_NAMREPLY, Params: []string{client.Nickname, memberStr}}
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_NAMREPLY, Params: []string{client.Nickname, channelPrefix, c.Name}, Trailing: memberStr}
client.Encode(&m)
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFNAMES, Params: []string{client.Nickname, c.Name}, Trailing: "End of NAMES list"}
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFNAMES, Params: []string{client.Nickname, c.Name}, Trailing: "End of NAMES list"}
client.Encode(&m)
}
// Part handles when a client leaves a channel
@ -134,7 +184,7 @@ func (c *Channel) Quit(client *Client, message string) {
return
}
m := irc.Message{Prefix: client.Prefix, Command: irc.QUIT}
m := irc.Message{Prefix: client.Prefix, Command: irc.QUIT, Params: []string{c.Name}}
if len(message) != 0 {
m.Trailing = message
}
@ -241,6 +291,7 @@ func (c *Channel) RemoveMemberMode(client *Client, mode ChannelMode) {
}
// GetMemberModes returns the Channel Modes active for a member
func (c *Channel) GetMemberModes(client *Client) *ChannelModeSet {
c.membersMutex.RLock()
defer c.membersMutex.RUnlock()
@ -332,3 +383,69 @@ func (c *Channel) TopicCommand(client *Client, topic string) {
return
}
// AddBanMask sets the channel ban mask
func (c *Channel) AddBanMask(mask string) {
masks := c.GetBanMasks()
masks[mask] = nil
c.AddModeWithValue(ChannelModeBan, masks)
}
// GetBanMasks gets the ban masks for the channel
func (c *Channel) GetBanMasks() map[string]interface{} {
val := c.GetMode(ChannelModeBan)
if val == nil {
return make(map[string]interface{}, 0)
}
return val.(map[string]interface{})
}
// AddExceptionMask sets the channel exception mask
func (c *Channel) AddExceptionMask(mask string) {
masks := c.GetExceptionMasks()
masks[mask] = nil
c.AddModeWithValue(ChannelModeExceptionMask, masks)
}
// GetExceptionMasks gets the exception masks for the channel
func (c *Channel) GetExceptionMasks() map[string]interface{} {
val := c.GetMode(ChannelModeExceptionMask)
if val == nil {
return make(map[string]interface{}, 0)
}
return val.(map[string]interface{})
}
// AddInvitationMask sets the channel invitation mask
func (c *Channel) AddInvitationMask(mask string) {
masks := c.GetInvitationMasks()
masks[mask] = nil
c.AddModeWithValue(ChannelModeInvitationMask, masks)
}
// GetInvitationMasks gets the invitation masks for the channel
func (c *Channel) GetInvitationMasks() map[string]interface{} {
val := c.GetMode(ChannelModeInvitationMask)
if val == nil {
return make(map[string]interface{}, 0)
}
return val.(map[string]interface{})
}
// SetLimit sets the channel member limit
func (c *Channel) SetLimit(limit int) {
c.AddModeWithValue(ChannelModeLimit, limit)
}
// GetLimit gets the member limit for the channel
func (c *Channel) GetLimit() int {
val := c.GetMode(ChannelModeLimit)
if val == nil {
return 0
}
return val.(int)
}

View File

@ -126,7 +126,7 @@ func (c *Client) Welcome() {
c.Prefix = &irc.Prefix{Name: c.Nickname, User: c.Name, Host: c.Host}
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WELCOME,
Params: []string{c.Nickname, c.Server.Config.Welcome}}
Params: []string{c.Nickname, "Welcome to the Internet Relay Network", c.Prefix.String()}}
err := c.Encode(&m)
if err != nil {
@ -134,7 +134,7 @@ func (c *Client) Welcome() {
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_YOURHOST,
Params: []string{c.Nickname, fmt.Sprintf("Your host is %s", c.Server.Config.Name)}}
Params: []string{c.Nickname, fmt.Sprintf("Your host is %s, running version %s", c.Server.Config.Name, c.Server.Config.Version)}}
err = c.Encode(&m)
if err != nil {
@ -157,10 +157,23 @@ func (c *Client) Welcome() {
return
}
m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTDSTART,
// Send MOTD
c.MOTD()
}
// MOTD returns the Message of the Day of the server to the client
func (c *Client) MOTD() {
if len(c.Server.Config.MOTD) == 0 {
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.ERR_NOMOTD, Params: []string{c.Nickname}, Trailing: "MOTD File is missing"}
c.Encode(&m)
}
m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTDSTART,
Params: []string{c.Nickname, fmt.Sprintf("%s - Message of the day", c.Server.Config.Name)}}
err = c.Encode(&m)
err := c.Encode(&m)
if err != nil {
return
}

View File

@ -2,6 +2,7 @@ package irc
import (
"fmt"
"strconv"
"strings"
"github.com/sorcix/irc"
@ -351,7 +352,7 @@ func whoLine(client *Client, channel *Channel, recipientClient string) string {
// 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, Trailing: "Not enough parameters"}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
@ -359,7 +360,7 @@ func TopicHandler(message *irc.Message, client *Client) {
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{channelName}, Trailing: "No such channel"}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOSUCHCHANNEL, Params: []string{client.Nickname, channelName}, Trailing: "No such channel"}
client.Encode(&m)
return
}
@ -380,7 +381,7 @@ 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, Trailing: "You are no longer marked as being away"}
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
}
@ -390,7 +391,7 @@ func AwayHandler(message *irc.Message, client *Client) {
client.AwayMessage = strings.Join(message.Params, " ")
}
client.AddMode(UserModeAway)
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_NOWAWAY, Trailing: "You have been marked as being away"}
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
@ -401,7 +402,7 @@ func AwayHandler(message *irc.Message, client *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, Trailing: "Not enough parameters"}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NEEDMOREPARAMS, Params: []string{client.Nickname}, Trailing: "Not enough parameters"}
client.Encode(&m)
return
}
@ -420,21 +421,21 @@ func ModeHandler(message *irc.Message, client *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, Trailing: "Not enough parameters"}
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, Trailing: "Cannot change mode for other users"}
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.UserModeSet.String()}}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{client.Nickname, client.UserModeSet.String()}}
client.Encode(&m)
return
}
@ -445,7 +446,7 @@ func UserModeHandler(message *irc.Message, client *Client) {
case ModeModifierAdd:
case ModeModifierRemove:
default:
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Trailing: "Unknown MODE flag"}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Params: []string{client.Nickname}, Trailing: "Unknown MODE flag"}
client.Encode(&m)
return
}
@ -454,7 +455,7 @@ func UserModeHandler(message *irc.Message, client *Client) {
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, Trailing: "Unknown MODE flag"}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Params: []string{client.Nickname}, Trailing: "Unknown MODE flag"}
client.Encode(&m)
return
}
@ -501,90 +502,173 @@ func ChannelModeHandler(message *irc.Message, client *Client) {
}
if len(message.Params) == 1 { // just channel name is provided
// return current settings for this user
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{channel.GetMemberModes(client).String()}}
// 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{channel.Name}, Trailing: "You're not channel operator"}
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
}
setLimit := false
getUsers := false
needMask := false
currentPlace := 1
for _, modeFlags := range message.Params[1:] {
currentPlace++
modifier := ModeModifier(modeFlags[0])
switch modifier {
case ModeModifierAdd:
case ModeModifierRemove:
default:
//modifier := ModeModifierQuery
/*
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Trailing: "Unknown MODE flag"}
client.Encode(&m)
return
*/
}
type fullFlag struct {
ModeModifier
ChannelMode
Param string
}
for _, modeFlag := range modeFlags[1:] {
mode := ChannelMode(modeFlag)
_, ok := ChannelModes[mode]
needsArgs := []fullFlag{}
changes := []fullFlag{}
argsCount := 0
if !ok {
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UMODEUNKNOWNFLAG, Trailing: "Unknown MODE flag"}
client.Encode(&m)
return
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
}
if modifier == ModeModifierAdd {
switch mode {
case ChannelModeCreator: // Can't make oneself a creator
case ChannelModeLimit:
setLimit = true
case ChannelModeOperator, ChannelModeVoice:
getUsers = true
default:
channel.AddMemberMode(client, mode)
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:
} else if modifier == ModeModifierRemove {
switch mode {
case ChannelModeCreator: // Can't remove oneself as creator
//case ChannelModeBan: // Can't remove oneself from being restricted
default:
channel.RemoveMemberMode(client, mode)
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)
}
}
}
}
if setLimit { // Get the number for setting the member limit cap
/*
limit, err := strconv.Atoi(message.Params[currentPlace])
if err != nil {
} 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
}
*/
}
if getUsers { // get users for either creating operators or adding voice
}
if needMask { // Set the ban mask
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, ""})
}
}
}
}
}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.RPL_UMODEIS, Params: []string{client.Nickname, client.UserModeSet.String()}}
client.Encode(&m)
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)
}
}
}
}

View File

@ -1,6 +1,9 @@
package irc
import "sync"
import (
"fmt"
"sync"
)
// UserMode - RFC 1459 Section 4.2.3.2 and RFC 2812 Section 3.1.5
type UserMode rune
@ -153,6 +156,13 @@ func (c *ChannelModeSet) AddMode(mode ChannelMode) {
}
// AddModeWithValue adds a ChannelMode as active with a value
func (c *Channel) AddModeWithValue(mode ChannelMode, value interface{}) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.modes[mode] = value
}
// RemoveMode removes the given mode from the active set
func (c *ChannelModeSet) RemoveMode(mode ChannelMode) {
c.mutex.Lock()
@ -170,14 +180,30 @@ func (c *ChannelModeSet) HasMode(mode ChannelMode) bool {
}
// GetMode determines if the ChannelModeSet contains the given ChannelMode
func (c *ChannelModeSet) GetMode(mode ChannelMode) interface{} {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.modes[mode]
}
// String returns the ChannelModeSet formatted for the MODE queries
func (c *ChannelModeSet) String() string {
s := ""
if len(c.modes) > 0 {
s = "+"
}
for m := range c.modes {
s := "+"
params := []interface{}{}
for m, param := range c.modes {
switch m {
case ChannelModeKey, ChannelModeLimit, ChannelModeModerated, ChannelModeAnonymous, ChannelModeInviteOnly, ChannelModePrivate, ChannelModeSecret, ChannelModeTopic:
default:
continue
}
s += string(m)
params = append(params, param)
}
for _, param := range params {
s += fmt.Sprintf(" %v", param)
}
return s

2
mux.go
View File

@ -26,7 +26,7 @@ func (c *CommandsMux) HandleFunc(command string, handler CommandHandlerFunc) {
func (c *CommandsMux) ServeIRC(message *irc.Message, client *Client) {
h, ok := c.commands[message.Command]
if !ok {
m := irc.Message{Command: irc.ERR_UNKNOWNCOMMAND}
m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_UNKNOWNCOMMAND, Params: []string{client.Nickname}}
client.Encode(&m)
return
}

View File

@ -32,7 +32,7 @@ type Server struct {
type ServerConfig struct {
Name string
MOTD string
Welcome string
Version string
TLSConfig *tls.Config
Addr string
@ -49,6 +49,12 @@ func NewServer(config ServerConfig) *Server {
s.clientsByNick = map[string]*Client{}
s.Prefix = &irc.Prefix{Name: config.Name}
s.channels = map[string]*Channel{}
if len(s.Config.Name) == 0 {
s.Config.Name = "localhost"
}
if len(s.Config.Version) == 0 {
s.Config.Version = "1.0"
}
return &s
}