From c9d9191a5af8775560faa96f1e4a689ce3b914d8 Mon Sep 17 00:00:00 2001 From: justin Date: Sat, 1 Aug 2015 22:46:58 -0400 Subject: [PATCH] Added channels. Closes #1. Support for the following Commands added. JOIN, PART, PRIVMSG, NOTICE, and WHO --- channel.go | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++ client.go | 72 ++++++++++----- commands.go | 189 ++++++++++++++++++++++++++++++++++++-- modes.go | 119 ++++++++++++++++++++++++ server.go | 64 +++++++++---- 5 files changed, 651 insertions(+), 47 deletions(-) create mode 100644 channel.go create mode 100644 modes.go diff --git a/channel.go b/channel.go new file mode 100644 index 0000000..e85b938 --- /dev/null +++ b/channel.go @@ -0,0 +1,254 @@ +package irc + +import ( + "strings" + "sync" + + "github.com/sorcix/irc" +) + +// Channel represents an IRC channel or room +type Channel struct { + Name string + Modes ChannelModeSet + Topic string + Key string + + members map[string]ChannelModeSet + membersMutex sync.RWMutex + + Server *Server +} + +// NewChannel creates and returns a new Channel +func NewChannel(s *Server, creator *Client) *Channel { + c := &Channel{} + c.members = map[string]ChannelModeSet{} + c.Server = s + c.Modes = NewChannelModeSet() + + return c +} + +// Join handles a client joining the channel and notifies other channel members +func (c *Channel) Join(client *Client, key string) { + _, ok := c.members[client.Nickname] + if ok { // client is already in this channel + return + } + if len(c.Key) != 0 { //if key is required, verify that client provided matching key + if c.Key != key { + m := irc.Message{Command: irc.ERR_BADCHANNELKEY} + err := client.Encode(&m) + if err != nil { + println(err.Error()) + } + } + } + operator := len(c.members) == 0 + + c.AddMember(client) + client.AddChannel(c) + + if operator { // Client is creating channel + // Creator should be a channel operator - Maybe check if it is a "safe" channel + c.AddMemberMode(client, ChannelModeOperator) + } + + var m irc.Message + + // Send topic if it exists + if len(c.Topic) != 0 { + m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_TOPIC, Params: []string{c.Name}, Trailing: c.Topic} + client.Encode(&m) + } + + //Notify existing members that new member is joining + + allMembers := make([]string, len(c.members)) + i := 0 + for member := range c.members { + allMembers[i] = member + i++ + } + + // send list of users in channel + for i := 0; i < (len(c.members)/20)+1; i++ { + memberStr := "= " + c.Name + " :" + 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 { + memberStr += mClient.Nickname + " " + + mClient.Encode(&m) + } + + } + m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_NAMREPLY, Params: []string{client.Nickname, 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"} + client.Encode(&m) + +} + +// Part handles when a client leaves a channel +func (c *Channel) Part(client *Client, message string) { + + _, ok := c.members[client.Nickname] + if !ok { // client is not in this channel + m := irc.Message{Prefix: client.Server.Prefix, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"} + client.Encode(&m) + return + } + + m := irc.Message{Prefix: client.Prefix, Command: irc.PART, Params: []string{c.Name}} + if len(message) != 0 { + m.Trailing = message + } + + c.SendMessage(&m) + + c.RemoveMember(client) + client.RemoveChannel(c) +} + +// Quit is when a client quits the server - at channel level, similar to part +func (c *Channel) Quit(client *Client, message string) { + + _, ok := c.members[client.Nickname] + if !ok { // client is not in this channel + //m := irc.Message{Prefix: &irc.Prefix{Name: client.Server.config.Name}, Command: irc.ERR_NOTONCHANNEL, Params: []string{c.Name}, Trailing: "You're not on that channel"} + //client.Encode(&m) + return + } + + m := irc.Message{Prefix: client.Prefix, Command: irc.QUIT} + if len(message) != 0 { + m.Trailing = message + } + c.SendMessage(&m) + + c.RemoveMember(client) +} + +// Message is when a Private Message is directed for this channel - forward the message to each member +func (c *Channel) Message(client *Client, message string) { + m := irc.Message{Prefix: client.Prefix, Command: irc.PRIVMSG, Params: []string{c.Name}, Trailing: message} + + c.SendMessageToOthers(&m, client) +} + +// Notice is when a Notice is directed for this channel - forward the notice to each member +func (c *Channel) Notice(client *Client, message string) { + m := irc.Message{Prefix: client.Prefix, Command: irc.NOTICE, Params: []string{c.Name}, Trailing: message} + + c.SendMessageToOthers(&m, client) +} + +// SendMessage allows sending an IRC message to all channel members +func (c *Channel) SendMessage(m *irc.Message) { + for member := range c.members { + mClient, _ := c.Server.GetClientByNick(member) + if mClient != nil { + mClient.Encode(m) + } + + } +} + +// SendMessageToOthers allows sending an IRC message to all other channel members +func (c *Channel) SendMessageToOthers(m *irc.Message, client *Client) { + for member := range c.members { + if member == client.Nickname { + continue + } + mClient, _ := c.Server.GetClientByNick(member) + + if mClient != nil { + mClient.Encode(m) + } + } +} + +// AddMember adds a member to the channel +func (c *Channel) AddMember(client *Client) { + c.membersMutex.Lock() + defer c.membersMutex.Unlock() + _, ok := c.members[client.Nickname] + if ok { // client is already a member + return + } + c.members[client.Nickname] = NewChannelModeSet() +} + +// RemoveMember removes a member from the channel +func (c *Channel) RemoveMember(client *Client) { + c.membersMutex.Lock() + defer c.membersMutex.Unlock() + delete(c.members, client.Nickname) + if len(c.members) == 0 { // NO more members + c.delete() + } +} + +// AddMemberMode adds a mode for the member of the channel +func (c *Channel) AddMemberMode(client *Client, mode ChannelMode) { + c.membersMutex.Lock() + defer c.membersMutex.Unlock() + m, ok := c.members[client.Nickname] + if ok { + m.AddMode(mode) + c.members[client.Nickname] = m + } +} + +// RemoveMemberMode removes a mode from the member of the channel +func (c *Channel) RemoveMemberMode(client *Client, mode ChannelMode) { + c.membersMutex.Lock() + defer c.membersMutex.Unlock() + m, ok := c.members[client.Nickname] + if ok { + m.RemoveMode(mode) + c.members[client.Nickname] = m + } + +} + +func (c *Channel) delete() { + if len(c.members) != 0 { + return + } + c.Server.RemoveChannel(c) + +} + +var channelStarters = map[uint8]interface{}{'&': nil, '#': nil, '+': nil, '!': nil} + +// validName checks if it meets parameters found in rfc2812 1.3 +func (c *Channel) validName() bool { + _, ok := channelStarters[c.Name[0]] + if !ok { + return false + } + + if strings.Contains(c.Name, " ") { + return false + } + + if strings.ContainsRune(c.Name, '\a') { // \a is the same as ^G + return false + } + + c.Name = strings.TrimSuffix(c.Name, ",") //trim comma off of the end + + return true +} diff --git a/client.go b/client.go index 4669fcb..d5dcb79 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ import ( "io" "net" "strings" + "sync" "time" "github.com/sorcix/irc" @@ -22,26 +23,30 @@ type Client struct { Prefix *irc.Prefix - server *Server + Server *Server authorized bool idleTimer *time.Timer quitTimer *time.Timer - awayMessage string + AwayMessage string + + channels map[string]*Channel + channelMutex sync.Mutex } func (s *Server) newClient(ircConn *irc.Conn, conn net.Conn) *Client { - client := &Client{Conn: ircConn, conn: conn, server: s} - client.authorized = len(s.config.Password) == 0 + client := &Client{Conn: ircConn, conn: conn, Server: s} + client.authorized = len(s.Config.Password) == 0 client.idleTimer = time.AfterFunc(time.Minute, client.idle) + client.channels = map[string]*Channel{} return client } // Close cleans up the IRC client and closes the connection func (c *Client) Close() error { - c.server.RemoveClient(c) - c.server.RemoveClientNick(c) + c.Server.RemoveClient(c) + c.Server.RemoveClientNick(c) return c.Conn.Close() } @@ -59,7 +64,7 @@ func (c *Client) Pong() { } func (c *Client) handleIncoming() { - c.server.AddClient(c) + c.Server.AddClient(c) for { message, err := c.Decode() if err != nil || message == nil { @@ -81,7 +86,7 @@ func (c *Client) handleIncoming() { c.quitTimer = nil } - c.server.CommandsMux.ServeIRC(message, c) + c.Server.CommandsMux.ServeIRC(message, c) } @@ -93,12 +98,16 @@ func (c *Client) idle() { } func (c *Client) quit() { + // Have client leave/part each channel + for _, channel := range c.GetChannels() { + channel.Quit(c, "") + } c.Quit() } // Quit sends the IRC Quit command and closes the connection func (c *Client) Quit() { - m := irc.Message{Prefix: &irc.Prefix{Name: c.server.config.Name}, Command: irc.QUIT, + m := irc.Message{Prefix: &irc.Prefix{Name: c.Server.Config.Name}, Command: irc.QUIT, Params: []string{c.Nickname}} c.Encode(&m) @@ -112,55 +121,55 @@ func (c *Client) Welcome() { // Have all client info now c.Prefix = &irc.Prefix{Name: c.Nickname, User: c.Username, Host: c.Host} - m := irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_WELCOME, - Params: []string{c.Nickname, c.server.config.Welcome}} + m := irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_WELCOME, + Params: []string{c.Nickname, c.Server.Config.Welcome}} err := c.Encode(&m) if err != nil { return } - 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)}} + 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)}} err = c.Encode(&m) if err != nil { return } - m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_CREATED, - Params: []string{c.Nickname, fmt.Sprintf("This server was created %s", c.server.created)}} + m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_CREATED, + Params: []string{c.Nickname, fmt.Sprintf("This server was created %s", c.Server.created)}} err = c.Encode(&m) if err != nil { return } - m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_MYINFO, - Params: []string{c.Nickname, fmt.Sprintf("%s - Golang IRC server", c.server.config.Name)}} + m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MYINFO, + Params: []string{c.Nickname, fmt.Sprintf("%s - Golang IRC server", c.Server.Config.Name)}} err = c.Encode(&m) if err != nil { return } - 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)}} + 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) if err != nil { return } - m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_MOTD, - Params: []string{c.Nickname, c.server.config.MOTD}} + m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_MOTD, + Params: []string{c.Nickname, c.Server.Config.MOTD}} err = c.Encode(&m) if err != nil { return } - m = irc.Message{Prefix: c.server.Prefix, Command: irc.RPL_ENDOFMOTD, + m = irc.Message{Prefix: c.Server.Prefix, Command: irc.RPL_ENDOFMOTD, Params: []string{c.Nickname, "End of MOTD"}} err = c.Encode(&m) @@ -168,3 +177,22 @@ func (c *Client) Welcome() { return } } + +// AddChannel adds a channel to the client's active list +func (c *Client) AddChannel(channel *Channel) { + c.channelMutex.Lock() + defer c.channelMutex.Unlock() + c.channels[channel.Name] = channel +} + +// RemoveChannel removes a channel to the client's active list +func (c *Client) RemoveChannel(channel *Channel) { + c.channelMutex.Lock() + defer c.channelMutex.Unlock() + delete(c.channels, channel.Name) +} + +// GetChannels gets a list of channels this client is joined to +func (c *Client) GetChannels() map[string]*Channel { + return c.channels +} diff --git a/commands.go b/commands.go index 74f3084..bd3c68b 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,7 @@ package irc import ( "fmt" + "strings" "github.com/sorcix/irc" ) @@ -37,7 +38,15 @@ func PongHandler(message *irc.Message, client *Client) { // Implemented according to RFC 1459 4.1.6 and RFC 2812 3.1.7 func QuitHandler(message *irc.Message, client *Client) { - m := irc.Message{Prefix: client.server.Prefix, Command: irc.ERROR, Trailing: "quit"} + var leavingMessage string + if len(message.Params) != 0 { + leavingMessage = message.Params[0] + } + 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() @@ -48,7 +57,7 @@ func QuitHandler(message *irc.Message, client *Client) { func NickHandler(message *irc.Message, client *Client) { var m irc.Message - name := client.server.config.Name + name := client.Server.Config.Name nickname := client.Nickname if len(message.Params) == 0 { @@ -59,7 +68,7 @@ func NickHandler(message *irc.Message, client *Client) { newNickname := message.Params[0] - _, found := client.server.ClientsByNick[newNickname] + _, found := client.Server.GetClientByNick(newNickname) switch { case !client.authorized: @@ -72,11 +81,11 @@ func NickHandler(message *irc.Message, client *Client) { default: if len(client.Nickname) == 0 && len(client.Username) != 0 { // Client is connected now, show MOTD ... client.Nickname = newNickname - client.server.AddClientNick(client) + client.Server.AddClientNick(client) client.Welcome() } else { //change client name client.Nickname = newNickname - client.server.UpdateClientNick(client, nickname) + client.Server.UpdateClientNick(client, nickname) //fmt.Println("Updating client name") } } @@ -91,7 +100,7 @@ func NickHandler(message *irc.Message, client *Client) { // Implemented according to RFC 1459 4.1.3 and RFC 2812 3.1.3 func UserHandler(message *irc.Message, client *Client) { var m irc.Message - serverName := client.server.config.Name + serverName := client.Server.Config.Name //nickname := client.Nickname if len(client.Username) != 0 { // Already registered @@ -125,3 +134,171 @@ func UserHandler(message *irc.Message, client *Client) { } } + +// JoinHandler is a CommandHandler to respond to IRC JOIN commands from a client +// Implemented according to RFC 1459 4.2.1 and RFC 2812 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 4.2.2 and RFC 2812 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 4.4.1 and RFC 2812 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) + 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 4.4.2 and RFC 2812 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 4.5.1 and RFC 2812 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 := fmt.Sprintf("%s %s %s %s %s %s %s%s :%d %s", client.Nickname, ch.Name, cl.Name, cl.Host, client.Server.Config.Name, cl.Nickname, "H", "", 0, cl.RealName) + 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, ch.Name}, Trailing: "End of WHO list"} + client.Encode(&m) + + } +} diff --git a/modes.go b/modes.go new file mode 100644 index 0000000..eec6667 --- /dev/null +++ b/modes.go @@ -0,0 +1,119 @@ +package irc + +import "sync" + +// UserMode - RFC 1459 4.2.3.2 and RFC 2812 3.1.5 +type UserMode rune + +const ( + UserModeAway UserMode = 'a' + UserModeInvisible UserMode = 'i' + UserModeWallOps UserMode = 'w' + UserModeRestricted UserMode = 'r' + UserModeOperator UserMode = 'o' + UserModeLocalOperator UserMode = 'O' + UserModeServerNotice UserMode = 's' //obsolete +) + +var UserModes = map[UserMode]interface{}{ + UserModeAway: nil, + UserModeInvisible: nil, + UserModeWallOps: nil, + UserModeRestricted: nil, + UserModeOperator: nil, + UserModeLocalOperator: nil, + UserModeServerNotice: nil, +} + +type UserModeSet struct { + userModes map[UserMode]interface{} + mutex sync.Mutex +} + +func NewUserModeSet() UserModeSet { + u := UserModeSet{} + u.userModes = map[UserMode]interface{}{} + return u +} + +func (u *UserModeSet) AddMode(mode UserMode) { + u.mutex.Lock() + defer u.mutex.Unlock() + u.userModes[mode] = nil + +} + +func (u *UserModeSet) RemoveMode(mode UserMode) { + u.mutex.Lock() + defer u.mutex.Unlock() + delete(u.userModes, mode) + +} + +func (u *UserModeSet) String() string { + s := "" + for m, _ := range u.userModes { + s += string(m) + } + + return s +} + +// ModeModifier - RFC 1459 4.2.3 and RFC 2812 3.1.5 +type ModeModifier rune + +const ( + ModeModifierAdd ModeModifier = '+' + ModeModifierRemove ModeModifier = '-' +) + +// ChannelMode - RFC 1459 4.2.3.1 and RFC 2812 3.2.3 and RFC 2811 4 +type ChannelMode rune + +const ( + ChannelModeCreator ChannelMode = 'O' + ChannelModeOperator ChannelMode = 'o' + ChannelModeVoice ChannelMode = 'v' + + ChannelModeAnonymous ChannelMode = 'a' + ChannelModeInviteOnly ChannelMode = 'i' + ChannelModeModerated ChannelMode = 'm' + ChannelModeNoOutsideMessages ChannelMode = 'n' + ChannelModeQuiet ChannelMode = 'q' + ChannelModePrivate ChannelMode = 'p' + ChannelModeSecret ChannelMode = 's' + ChannelModeReOp ChannelMode = 'r' + ChannelModeTopic ChannelMode = 't' + + ChannelModeKey ChannelMode = 'k' + ChannelModeLimit ChannelMode = 'l' + + ChannelModeBan ChannelMode = 'b' + ChannelModeExceptionMask ChannelMode = 'e' + ChannelModeInvitationMask ChannelMode = 'I' +) + +type ChannelModeSet struct { + Modes map[ChannelMode]interface{} + mutex sync.Mutex +} + +func NewChannelModeSet() ChannelModeSet { + c := ChannelModeSet{} + c.Modes = map[ChannelMode]interface{}{} + return c +} + +func (c *ChannelModeSet) AddMode(mode ChannelMode) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.Modes[mode] = nil + +} + +func (c *ChannelModeSet) RemoveMode(mode ChannelMode) { + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.Modes, mode) + +} diff --git a/server.go b/server.go index dc17dc8..9721904 100644 --- a/server.go +++ b/server.go @@ -12,17 +12,20 @@ import ( // Server represents an IRC server type Server struct { - config ServerConfig + Config ServerConfig - Clients map[net.Addr]*Client + clients map[net.Addr]*Client clientMutex sync.RWMutex - ClientsByNick map[string]*Client + clientsByNick map[string]*Client clientByNickMutex sync.RWMutex Prefix *irc.Prefix CommandsMux CommandsMux created time.Time + + channels map[string]*Channel + channelMutex sync.RWMutex } // ServerConfig contains configuration data for seeding a server @@ -39,13 +42,13 @@ type ServerConfig struct { // NewServer creates and returns a new Server based on the provided config func NewServer(config ServerConfig) *Server { s := Server{} - s.config = config - s.Clients = map[net.Addr]*Client{} + s.Config = config + s.clients = map[net.Addr]*Client{} s.CommandsMux = NewCommandsMux() s.created = time.Now() - s.ClientsByNick = map[string]*Client{} + s.clientsByNick = map[string]*Client{} s.Prefix = &irc.Prefix{Name: config.Name} - + s.channels = map[string]*Channel{} return &s } @@ -53,60 +56,61 @@ func NewServer(config ServerConfig) *Server { func (s *Server) AddClient(client *Client) { s.clientMutex.Lock() defer s.clientMutex.Unlock() - s.Clients[client.conn.RemoteAddr()] = client + s.clients[client.conn.RemoteAddr()] = client } // RemoveClient removes a client func (s *Server) RemoveClient(client *Client) { s.clientMutex.Lock() defer s.clientMutex.Unlock() - delete(s.Clients, client.conn.RemoteAddr()) + delete(s.clients, client.conn.RemoteAddr()) } // GetClient finds a client by its address and returns it func (s *Server) GetClient(addr net.Addr) *Client { s.clientMutex.RLock() defer s.clientMutex.RUnlock() - return s.Clients[addr] + return s.clients[addr] } // AddClientNick adds a client based on its nickname func (s *Server) AddClientNick(client *Client) { s.clientByNickMutex.Lock() defer s.clientByNickMutex.Unlock() - s.ClientsByNick[client.Nickname] = client + s.clientsByNick[client.Nickname] = client } // RemoveClientNick removes a client based on its nickname func (s *Server) RemoveClientNick(client *Client) { s.clientByNickMutex.Lock() defer s.clientByNickMutex.Unlock() - delete(s.ClientsByNick, client.Nickname) + delete(s.clientsByNick, client.Nickname) } // UpdateClientNick updates the nickname of a client as it is stored by the server func (s *Server) UpdateClientNick(client *Client, oldNick string) { s.clientByNickMutex.Lock() defer s.clientByNickMutex.Unlock() - delete(s.ClientsByNick, oldNick) - s.ClientsByNick[client.Nickname] = client + delete(s.clientsByNick, oldNick) + s.clientsByNick[client.Nickname] = client } // GetClientByNick returns a client with the corresponding nickname -func (s *Server) GetClientByNick(nick string) *Client { +func (s *Server) GetClientByNick(nick string) (*Client, bool) { s.clientByNickMutex.RLock() defer s.clientByNickMutex.RUnlock() - return s.ClientsByNick[nick] + c, ok := s.clientsByNick[nick] + return c, ok } // Start the server listening on the configured port func (s *Server) Start() { var listener net.Listener var err error - if s.config.TLSConfig != nil { - listener, err = tls.Listen("tcp", s.config.Addr, s.config.TLSConfig) + if s.Config.TLSConfig != nil { + listener, err = tls.Listen("tcp", s.Config.Addr, s.Config.TLSConfig) } else { - listener, err = net.Listen("tcp", s.config.Addr) + listener, err = net.Listen("tcp", s.Config.Addr) } if err != nil { @@ -134,3 +138,25 @@ func (s *Server) Start() { } } + +// AddChannel adds an active channel +func (s *Server) AddChannel(channel *Channel) { + s.channelMutex.Lock() + defer s.channelMutex.Unlock() + s.channels[channel.Name] = channel +} + +// RemoveChannel removes a channel from the active listing +func (s *Server) RemoveChannel(channel *Channel) { + s.channelMutex.Lock() + defer s.channelMutex.Unlock() + delete(s.channels, channel.Name) +} + +// GetChannel finds and returns an active channel with a matching name if it exists +func (s *Server) GetChannel(channelName string) (*Channel, bool) { + s.channelMutex.RLock() + defer s.channelMutex.RUnlock() + c, ok := s.channels[channelName] + return c, ok +}