diff --git a/irc/channel.go b/irc/channel.go index 8055a38f..34b6cc45 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -6,7 +6,6 @@ package irc import ( - "fmt" "log" "strconv" ) @@ -66,7 +65,7 @@ func (channel *Channel) Names(client *Client) { } if len(buffer)+1+len(nick) > maxNamLen { - client.Send(nil, client.server.nameString, RPL_NAMREPLY, "=", channel.nameString, buffer) + client.Send(nil, client.server.nameString, RPL_NAMREPLY, client.nickString, "=", channel.nameString, buffer) buffer = nick continue } @@ -75,8 +74,8 @@ func (channel *Channel) Names(client *Client) { buffer += nick } - client.Send(nil, client.server.nameString, RPL_NAMREPLY, "=", channel.nameString, buffer) - client.Send(nil, client.server.nameString, RPL_ENDOFNAMES, channel.nameString, "End of NAMES list") + client.Send(nil, client.server.nameString, RPL_NAMREPLY, client.nickString, "=", channel.nameString, buffer) + client.Send(nil, client.server.nameString, RPL_ENDOFNAMES, client.nickString, channel.nameString, "End of NAMES list") } func (channel *Channel) ClientIsOperator(client *Client) bool { @@ -158,7 +157,7 @@ func (channel *Channel) ModeString(client *Client) (str string) { str += " " + strconv.FormatUint(channel.userLimit, 10) } - return + return str } func (channel *Channel) IsFull() bool { @@ -199,6 +198,10 @@ func (channel *Channel) Join(client *Client, key string) { return } + for member := range channel.members { + member.Send(nil, client.nickMaskString, "JOIN", channel.nameString) + } + client.channels.Add(channel) channel.members.Add(client) if !channel.flags[Persistent] && (len(channel.members) == 1) { @@ -207,16 +210,8 @@ func (channel *Channel) Join(client *Client, key string) { } client.Send(nil, client.nickMaskString, "JOIN", channel.nameString) - return - //TODO(dan): should we be continuing here???? - // return was above this originally, is it required? - /* - for member := range channel.members { - member.Reply(reply) - } - channel.GetTopic(client) - channel.Names(client) - */ + channel.GetTopic(client) + channel.Names(client) } func (channel *Channel) Part(client *Client, message string) { @@ -233,17 +228,17 @@ func (channel *Channel) Part(client *Client, message string) { func (channel *Channel) GetTopic(client *Client) { if !channel.members.Has(client) { - client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel") + client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, client.nickString, channel.nameString, "You're not on that channel") return } if channel.topic == "" { - // clients appear not to expect this - //replier.Reply(RplNoTopic(channel)) + client.Send(nil, client.server.nameString, RPL_NOTOPIC, client.nickString, channel.nameString, "No topic is set") return } - client.Send(nil, client.server.nameString, RPL_TOPIC, channel.nameString, channel.topic) + client.Send(nil, client.server.nameString, RPL_TOPIC, client.nickString, channel.nameString, channel.topic) + //TODO(dan): show topic time and setter here too } func (channel *Channel) SetTopic(client *Client, topic string) { @@ -322,16 +317,11 @@ func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode, } func (channel *Channel) applyModeMember(client *Client, mode ChannelMode, - op ModeOp, nick string) bool { - if !channel.ClientIsOperator(client) { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") - return false - } - + op ModeOp, nick string) *ChannelModeChange { if nick == "" { //TODO(dan): shouldn't this be handled before it reaches this function? client.Send(nil, client.server.nameString, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters") - return false + return nil } target := channel.server.clients.Get(Name(nick)) @@ -339,30 +329,38 @@ func (channel *Channel) applyModeMember(client *Client, mode ChannelMode, //TODO(dan): investigate using NOSUCHNICK and NOSUCHCHANNEL specifically as that other IRCd (insp?) does, // since I think that would make sense client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, nick, "No such nick") - return false + return nil } if !channel.members.Has(target) { client.Send(nil, client.server.nameString, ERR_USERNOTINCHANNEL, client.nickString, channel.nameString, "They aren't on that channel") - return false + return nil } switch op { case Add: if channel.members[target][mode] { - return false + return nil } channel.members[target][mode] = true - return true + return &ChannelModeChange{ + op: Add, + mode: mode, + arg: nick, + } case Remove: if !channel.members[target][mode] { - return false + return nil } channel.members[target][mode] = false - return true + return &ChannelModeChange{ + op: Remove, + mode: mode, + arg: nick, + } } - return false + return nil } func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) { @@ -404,117 +402,6 @@ func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeO return false } -func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) bool { - switch change.mode { - case BanMask, ExceptMask, InviteMask: - return channel.applyModeMask(client, change.mode, change.op, - NewName(change.arg)) - - case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Persistent, Secret: - return channel.applyModeFlag(client, change.mode, change.op) - - case Key: - if !channel.ClientIsOperator(client) { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") - return false - } - - switch change.op { - case Add: - if change.arg == "" { - client.Send(nil, client.server.nameString, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters") - return false - } - key := change.arg - if key == channel.key { - return false - } - - channel.key = key - return true - - case Remove: - channel.key = "" - return true - } - - case UserLimit: - limit, err := strconv.ParseUint(change.arg, 10, 64) - if err != nil { - client.Send(nil, client.server.nameString, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters") - return false - } - if (limit == 0) || (limit == channel.userLimit) { - return false - } - - channel.userLimit = limit - return true - - case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: - var hasPrivs bool - - // make sure client has privs to edit the given prefix - for _, mode := range ChannelPrivModes { - if channel.members[client][mode] { - hasPrivs = true - - // Admins can't give other people Admin or remove it from others, - // standard for that channel mode, we worry about this later - if mode == ChannelAdmin && change.mode == ChannelAdmin { - hasPrivs = false - } - - break - } else if mode == change.mode { - break - } - } - - name := NewName(change.arg) - - if !hasPrivs { - if change.op == Remove && name.ToLower() == client.nick.ToLower() { - // success! - } else { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") - return false - } - } - - return channel.applyModeMember(client, change.mode, change.op, name.String()) - - default: - client.Send(nil, client.server.nameString, ERR_UNKNOWNMODE, change.mode.String(), fmt.Sprintf(":is an unknown mode char to me for %s", channel)) - } - return false -} - -func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { - if len(changes) == 0 { - client.Send(nil, client.server.nameString, RPL_CHANNELMODEIS, channel.nameString, channel.ModeString(client)) - return - } - - applied := make(ChannelModeChanges, 0) - for _, change := range changes { - if channel.applyMode(client, change) { - applied = append(applied, change) - } - } - - if len(applied) > 0 { - appliedString := applied.String() - for member := range channel.members { - member.Send(nil, client.nickMaskString, "MODE", channel.nameString, appliedString) - } - - if err := channel.Persist(); err != nil { - log.Println("Channel.Persist:", channel, err) - } - } -} - func (channel *Channel) Persist() (err error) { if channel.flags[Persistent] { _, err = channel.server.db.Exec(` diff --git a/irc/modes.go b/irc/modes.go index 08515c11..16982a67 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -132,9 +132,7 @@ var ( func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { name := NewName(msg.Params[0]) if name.IsChannel() { - // return cmodeHandler(server, client, msg) - client.Notice("CMODEs are not yet supported!") - return false + return cmodeHandler(server, client, msg) } else { return umodeHandler(server, client, msg) } @@ -164,14 +162,15 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // assemble changes changes := make(ModeChanges, 0) + applied := make(ModeChanges, 0) if len(msg.Params) > 1 { - modeArg := msg.Params[0] + modeArg := msg.Params[1] op := ModeOp(modeArg[0]) if (op == Add) || (op == Remove) { modeArg = modeArg[1:] } else { - client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "MODE", "Mode string could not be parsed correctly") + client.Send(nil, server.nameString, ERR_UNKNOWNMODE, client.nickString, string(modeArg[1]), "is an unknown mode character to me") return false } @@ -195,14 +194,14 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { continue } target.flags[change.mode] = true - changes = append(changes, change) + applied = append(applied, change) case Remove: if !target.flags[change.mode] { continue } delete(target.flags, change.mode) - changes = append(changes, change) + applied = append(applied, change) } case Operator, LocalOperator: @@ -211,7 +210,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { continue } delete(target.flags, change.mode) - changes = append(changes, change) + applied = append(applied, change) } } } @@ -225,15 +224,153 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return false } -/* -func (msg *ChannelModeCommand) HandleServer(server *Server) { - client := msg.Client() - channel := server.channels.Get(msg.channel) +// MODE [ [...]] +func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { + channelName := NewName(msg.Params[0]) + channel := server.channels.Get(channelName) + if channel == nil { - client.ErrNoSuchChannel(msg.channel) - return + client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, msg.Params[0], "No such channel") + return false } - channel.Mode(client, msg.changes) + // assemble changes + //TODO(dan): split out assembling changes into func that returns changes, err + changes := make(ChannelModeChanges, 0) + applied := make(ChannelModeChanges, 0) + + if len(msg.Params) > 1 { + modeArg := msg.Params[1] + op := ModeOp(modeArg[0]) + if (op == Add) || (op == Remove) { + modeArg = modeArg[1:] + } else { + client.Send(nil, server.nameString, ERR_UNKNOWNMODE, client.nickString, string(modeArg[1]), "is an unknown mode character to me") + return false + } + + skipArgs := 2 + for _, mode := range modeArg { + if mode == '-' || mode == '+' { + op = ModeOp(mode) + continue + } + change := ChannelModeChange{ + mode: ChannelMode(mode), + op: op, + } + + // put arg into modechange if needed + switch ChannelMode(mode) { + case BanMask, ExceptMask, InviteMask: + if len(msg.Params) > skipArgs { + change.arg = msg.Params[skipArgs] + skipArgs += 1 + } else { + change.op = List + } + case Key, UserLimit, ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: + if len(msg.Params) > skipArgs { + change.arg = msg.Params[skipArgs] + skipArgs += 1 + } else { + continue + } + } + + applied = append(applied, &change) + } + + for _, change := range changes { + switch change.mode { + case BanMask, ExceptMask, InviteMask: + mask := change.arg + list := channel.lists[change.mode] + if list == nil { + // This should never happen, but better safe than panicky. + client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "MODE", "Could not complete MODE command") + return false + } + + if (change.op == List) || (mask == "") { + channel.ShowMaskList(client, change.mode) + continue + } + + switch change.op { + case Add: + list.Add(Name(mask)) + applied = append(applied, change) + + case Remove: + list.Remove(Name(mask)) + applied = append(applied, change) + } + + case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Persistent, Secret: + switch change.op { + case Add: + if channel.flags[change.mode] { + continue + } + channel.flags[change.mode] = true + applied = append(applied, change) + + case Remove: + if !channel.flags[change.mode] { + continue + } + delete(channel.flags, change.mode) + applied = append(applied, change) + } + + case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: + // make sure client has privs to edit the given prefix + var hasPrivs bool + + for _, mode := range ChannelPrivModes { + if channel.members[client][mode] { + hasPrivs = true + + // Admins can't give other people Admin or remove it from others, + // standard for that channel mode, we worry about this later + if mode == ChannelAdmin && change.mode == ChannelAdmin { + hasPrivs = false + } + + break + } else if mode == change.mode { + break + } + } + + name := NewName(change.arg) + + if !hasPrivs { + if change.op == Remove && name.ToLower() == client.nick.ToLower() { + // success! + } else { + client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") + continue + } + } + + change := channel.applyModeMember(client, change.mode, change.op, change.arg) + if change != nil { + applied = append(changes, change) + } + } + } + } + + if len(applied) > 0 { + //TODO(dan): we should change the name of String and make it return a slice here + args := append([]string{channel.nameString}, strings.Split(applied.String(), " ")...) + client.Send(nil, client.nickMaskString, "MODE", args...) + } else { + //TODO(dan): we should just make ModeString return a slice here + args := append([]string{client.nickString, channel.nameString}, strings.Split(channel.ModeString(client), " ")...) + client.Send(nil, client.nickMaskString, RPL_CHANNELMODEIS, args...) + } + return false } -*/ diff --git a/irc/numerics.go b/irc/numerics.go index 3f4ce6cb..b92219a6 100644 --- a/irc/numerics.go +++ b/irc/numerics.go @@ -60,6 +60,7 @@ const ( RPL_LISTEND = "323" RPL_CHANNELMODEIS = "324" RPL_UNIQOPIS = "325" + RPL_CREATIONTIME = "329" RPL_NOTOPIC = "331" RPL_TOPIC = "332" RPL_INVITING = "341"