From 5e72409695d34d1b9cd5bfdfa0a22e698effe030 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Tue, 11 Oct 2016 23:51:46 +1000 Subject: [PATCH] Move from ascii(ish) unicode encoding to prelim rfc7700 using functions instead --- irc/accounts.go | 43 ++--- irc/capability.go | 10 +- irc/channel.go | 143 ++++++++--------- irc/client.go | 125 ++++++++------- irc/client_lookup_set.go | 78 ++++++---- irc/commands.go | 4 +- irc/config.go | 22 ++- irc/isupport.go | 2 +- irc/modes.go | 58 ++++--- irc/net.go | 20 +-- irc/nickname.go | 40 ++--- irc/registration.go | 58 +++---- irc/server.go | 328 ++++++++++++++++++++++----------------- irc/strings.go | 120 ++++++-------- irc/types.go | 22 +-- irc/whowas.go | 29 ++-- 16 files changed, 587 insertions(+), 515 deletions(-) diff --git a/irc/accounts.go b/irc/accounts.go index dc2ef8b2..4b88b530 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -80,9 +80,9 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) // sasl abort if len(msg.Params) == 1 && msg.Params[0] == "*" { if client.saslInProgress { - client.Send(nil, server.nameString, ERR_SASLABORTED, client.nickString, "SASL authentication aborted") + client.Send(nil, server.name, ERR_SASLABORTED, client.nick, "SASL authentication aborted") } else { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed") } client.saslInProgress = false client.saslMechanism = "" @@ -98,9 +98,9 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) if mechanismIsEnabled { client.saslInProgress = true client.saslMechanism = mechanism - client.Send(nil, server.nameString, "AUTHENTICATE", "+") + client.Send(nil, server.name, "AUTHENTICATE", "+") } else { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed") } return false @@ -110,7 +110,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) rawData := msg.Params[0] if len(rawData) > 400 { - client.Send(nil, server.nameString, ERR_SASLTOOLONG, client.nickString, "SASL message too long") + client.Send(nil, server.name, ERR_SASLTOOLONG, client.nick, "SASL message too long") client.saslInProgress = false client.saslMechanism = "" client.saslValue = "" @@ -119,7 +119,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) client.saslValue += rawData // allow 4 'continuation' lines before rejecting for length if len(client.saslValue) > 400*4 { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Passphrase too long") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Passphrase too long") client.saslInProgress = false client.saslMechanism = "" client.saslValue = "" @@ -136,7 +136,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) if client.saslValue != "+" { data, err = base64.StdEncoding.DecodeString(client.saslValue) if err != nil { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Invalid b64 encoding") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid b64 encoding") client.saslInProgress = false client.saslMechanism = "" client.saslValue = "" @@ -149,7 +149,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) // like 100% not required, but it's good to be safe I guess if !handlerExists { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed") client.saslInProgress = false client.saslMechanism = "" client.saslValue = "" @@ -169,7 +169,7 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value [] splitValue := bytes.Split(value, []byte{'\000'}) if len(splitValue) != 3 { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: Invalid auth blob") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Invalid auth blob") return false } @@ -177,17 +177,20 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value [] authzid := string(splitValue[1]) if accountKey != authzid { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed: authcid and authzid should be the same") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: authcid and authzid should be the same") return false } - // casefolding, rough for now bit will improve later. // keep it the same as in the REG CREATE stage - accountKey = NewName(accountKey).String() + accountKey, err := CasefoldName(accountKey) + if err != nil { + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed: Bad account name") + return false + } // load and check acct data all in one update to prevent races. // as noted elsewhere, change to proper locking for Account type later probably - err := server.store.Update(func(tx *buntdb.Tx) error { + err = server.store.Update(func(tx *buntdb.Tx) error { creds, err := loadAccountCredentials(tx, accountKey) if err != nil { return err @@ -213,19 +216,19 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value [] }) if err != nil { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed") return false } - client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name)) - client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful") + client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name)) + client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "SASL authentication successful") return false } // authExternalHandler parses the SASL EXTERNAL mechanism. func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool { if client.certfp == "" { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed, you are not connecting with a certificate") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed, you are not connecting with a caertificate") return false } @@ -261,11 +264,11 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value }) if err != nil { - client.Send(nil, server.nameString, ERR_SASLFAIL, client.nickString, "SASL authentication failed") + client.Send(nil, server.name, ERR_SASLFAIL, client.nick, "SASL authentication failed") return false } - client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name)) - client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful") + client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name)) + client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "SASL authentication successful") return false } diff --git a/irc/capability.go b/irc/capability.go index 4e967a2e..6fe9ff53 100644 --- a/irc/capability.go +++ b/irc/capability.go @@ -107,23 +107,23 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // client.server needs to be here to workaround a parsing bug in weechat 1.4 // and let it connect to the server (otherwise it doesn't respond to the CAP // message with anything and just hangs on connection) - client.Send(nil, server.nameString, "CAP", client.nickString, subCommand, SupportedCapabilities.String()) + client.Send(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String()) case "LIST": - client.Send(nil, server.nameString, "CAP", client.nickString, subCommand, client.capabilities.String()) + client.Send(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String()) case "REQ": // make sure all capabilities actually exist for capability := range capabilities { if !SupportedCapabilities[capability] { - client.Send(nil, server.nameString, "CAP", client.nickString, "NAK", capString) + client.Send(nil, server.name, "CAP", client.nick, "NAK", capString) return false } } for capability := range capabilities { client.capabilities[capability] = true } - client.Send(nil, server.nameString, "CAP", client.nickString, "ACK", capString) + client.Send(nil, server.name, "CAP", client.nick, "ACK", capString) case "END": if !client.registered { @@ -132,7 +132,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } default: - client.Send(nil, server.nameString, ERR_INVALIDCAPCMD, client.nickString, subCommand, "Invalid CAP subcommand") + client.Send(nil, server.name, ERR_INVALIDCAPCMD, client.nick, subCommand, "Invalid CAP subcommand") } return false } diff --git a/irc/channel.go b/irc/channel.go index a95bebc7..08139ad3 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -6,29 +6,36 @@ package irc import ( + "fmt" "log" "strconv" "time" ) type Channel struct { - flags ChannelModeSet - lists map[ChannelMode]*UserMaskSet - key string - members MemberSet - name Name - nameString string - server *Server - createdTime time.Time - topic string - topicSetBy string - topicSetTime time.Time - userLimit uint64 + flags ChannelModeSet + lists map[ChannelMode]*UserMaskSet + key string + members MemberSet + name string + nameCasefolded string + server *Server + createdTime time.Time + topic string + topicSetBy string + topicSetTime time.Time + userLimit uint64 } // NewChannel creates a new channel from a `Server` and a `name` // string, which must be unique on the server. -func NewChannel(s *Server, name Name, addDefaultModes bool) *Channel { +func NewChannel(s *Server, name string, addDefaultModes bool) *Channel { + casefoldedName, err := CasefoldChannel(name) + if err != nil { + log.Println(fmt.Sprintf("ERROR: Channel name is bad: [%s]", name), err.Error()) + return nil + } + channel := &Channel{ flags: make(ChannelModeSet), lists: map[ChannelMode]*UserMaskSet{ @@ -36,10 +43,10 @@ func NewChannel(s *Server, name Name, addDefaultModes bool) *Channel { ExceptMask: NewUserMaskSet(), InviteMask: NewUserMaskSet(), }, - members: make(MemberSet), - name: name, - nameString: name.String(), - server: s, + members: make(MemberSet), + name: name, + nameCasefolded: casefoldedName, + server: s, } if addDefaultModes { @@ -60,7 +67,7 @@ func (channel *Channel) IsEmpty() bool { func (channel *Channel) Names(client *Client) { currentNicks := channel.Nicks(client) // assemble and send replies - maxNamLen := 480 - len(client.server.nameString) - len(client.nickString) + maxNamLen := 480 - len(client.server.name) - len(client.nick) var buffer string for _, nick := range currentNicks { if buffer == "" { @@ -69,7 +76,7 @@ func (channel *Channel) Names(client *Client) { } if len(buffer)+1+len(nick) > maxNamLen { - client.Send(nil, client.server.nameString, RPL_NAMREPLY, client.nickString, "=", channel.nameString, buffer) + client.Send(nil, client.server.name, RPL_NAMREPLY, client.nick, "=", channel.name, buffer) buffer = nick continue } @@ -78,8 +85,8 @@ func (channel *Channel) Names(client *Client) { buffer += nick } - 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") + client.Send(nil, client.server.name, RPL_NAMREPLY, client.nick, "=", channel.name, buffer) + client.Send(nil, client.server.name, RPL_ENDOFNAMES, client.nick, channel.name, "End of NAMES list") } func (channel *Channel) ClientIsOperator(client *Client) bool { @@ -117,25 +124,21 @@ func (channel *Channel) Nicks(target *Client) []string { if isUserhostInNames { nicks[i] += client.nickMaskString } else { - nicks[i] += client.nickString + nicks[i] += client.nick } i += 1 } return nicks } -func (channel *Channel) Id() Name { +func (channel *Channel) Id() string { return channel.name } -func (channel *Channel) Nick() Name { +func (channel *Channel) Nick() string { return channel.name } -func (channel *Channel) String() string { - return channel.Id().String() -} - // func (channel *Channel) ModeString(client *Client) (str string) { isMember := client.flags[Operator] || channel.members.Has(client) @@ -185,33 +188,33 @@ func (channel *Channel) Join(client *Client, key string) { } if channel.IsFull() { - client.Send(nil, client.server.nameString, ERR_CHANNELISFULL, channel.nameString, "Cannot join channel (+l)") + client.Send(nil, client.server.name, ERR_CHANNELISFULL, channel.name, "Cannot join channel (+l)") return } if !channel.CheckKey(key) { - client.Send(nil, client.server.nameString, ERR_BADCHANNELKEY, channel.nameString, "Cannot join channel (+k)") + client.Send(nil, client.server.name, ERR_BADCHANNELKEY, channel.name, "Cannot join channel (+k)") return } isInvited := channel.lists[InviteMask].Match(client.UserHost()) if channel.flags[InviteOnly] && !isInvited { - client.Send(nil, client.server.nameString, ERR_INVITEONLYCHAN, channel.nameString, "Cannot join channel (+i)") + client.Send(nil, client.server.name, ERR_INVITEONLYCHAN, channel.name, "Cannot join channel (+i)") return } if channel.lists[BanMask].Match(client.UserHost()) && !isInvited && !channel.lists[ExceptMask].Match(client.UserHost()) { - client.Send(nil, client.server.nameString, ERR_BANNEDFROMCHAN, channel.nameString, "Cannot join channel (+b)") + client.Send(nil, client.server.name, ERR_BANNEDFROMCHAN, channel.name, "Cannot join channel (+b)") return } for member := range channel.members { if member.capabilities[ExtendedJoin] { - member.Send(nil, client.nickMaskString, "JOIN", channel.nameString, client.account.Name, client.realname) + member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname) } else { - member.Send(nil, client.nickMaskString, "JOIN", channel.nameString) + member.Send(nil, client.nickMaskString, "JOIN", channel.name) } } @@ -224,9 +227,9 @@ func (channel *Channel) Join(client *Client, key string) { } if client.capabilities[ExtendedJoin] { - client.Send(nil, client.nickMaskString, "JOIN", channel.nameString, client.account.Name, client.realname) + client.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname) } else { - client.Send(nil, client.nickMaskString, "JOIN", channel.nameString) + client.Send(nil, client.nickMaskString, "JOIN", channel.name) } channel.GetTopic(client) channel.Names(client) @@ -234,39 +237,39 @@ func (channel *Channel) Join(client *Client, key string) { func (channel *Channel) Part(client *Client, message string) { 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.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return } for member := range channel.members { - member.Send(nil, client.nickMaskString, "PART", channel.nameString, message) + member.Send(nil, client.nickMaskString, "PART", channel.name, message) } channel.Quit(client) } func (channel *Channel) GetTopic(client *Client) { if !channel.members.Has(client) { - client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, client.nickString, channel.nameString, "You're not on that channel") + client.Send(nil, client.server.name, ERR_NOTONCHANNEL, client.nick, channel.name, "You're not on that channel") return } if channel.topic == "" { - client.Send(nil, client.server.nameString, RPL_NOTOPIC, client.nickString, channel.nameString, "No topic is set") + client.Send(nil, client.server.name, RPL_NOTOPIC, client.nick, channel.name, "No topic is set") return } - client.Send(nil, client.server.nameString, RPL_TOPIC, client.nickString, channel.nameString, channel.topic) - client.Send(nil, client.server.nameString, RPL_TOPICTIME, client.nickString, channel.nameString, channel.topicSetBy, strconv.FormatInt(channel.topicSetTime.Unix(), 10)) + client.Send(nil, client.server.name, RPL_TOPIC, client.nick, channel.name, channel.topic) + client.Send(nil, client.server.name, RPL_TOPICTIME, client.nick, channel.name, channel.topicSetBy, strconv.FormatInt(channel.topicSetTime.Unix(), 10)) } func (channel *Channel) SetTopic(client *Client, topic string) { if !(client.flags[Operator] || 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.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return } if channel.flags[OpOnlyTopic] && !channel.ClientIsOperator(client) { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") return } @@ -275,11 +278,11 @@ func (channel *Channel) SetTopic(client *Client, topic string) { } channel.topic = topic - channel.topicSetBy = client.nickString + channel.topicSetBy = client.nick channel.topicSetTime = time.Now() for member := range channel.members { - member.Send(nil, client.nickMaskString, "TOPIC", channel.nameString, channel.topic) + member.Send(nil, client.nickMaskString, "TOPIC", channel.name, channel.topic) } channel.Persist() @@ -301,21 +304,21 @@ func (channel *Channel) CanSpeak(client *Client) bool { func (channel *Channel) PrivMsg(client *Client, message string) { if !channel.CanSpeak(client) { - client.Send(nil, client.server.nameString, ERR_CANNOTSENDTOCHAN, channel.nameString, "Cannot send to channel") + client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return } for member := range channel.members { if member == client { continue } - member.SendFromClient(client, nil, client.nickMaskString, "PRIVMSG", channel.nameString, message) + member.SendFromClient(client, nil, client.nickMaskString, "PRIVMSG", channel.name, message) } } func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode, op ModeOp) bool { if !channel.ClientIsOperator(client) { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") return false } @@ -341,20 +344,19 @@ func (channel *Channel) applyModeMember(client *Client, mode ChannelMode, 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") + client.Send(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters") return nil } - target := channel.server.clients.Get(Name(nick)) - if target == nil { - //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") + casefoldedName, err := CasefoldName(nick) + target := channel.server.clients.Get(casefoldedName) + if err != nil || target == nil { + client.Send(nil, client.server.name, ERR_NOSUCHNICK, nick, "No such nick") 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") + client.Send(nil, client.server.name, ERR_USERNOTINCHANNEL, client.nick, channel.name, "They aren't on that channel") return nil } @@ -394,8 +396,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) { client.RplEndOfMaskList(mode, channel)*/ } -func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, - mask Name) bool { +func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, mask string) bool { list := channel.lists[mode] if list == nil { // This should never happen, but better safe than panicky. @@ -408,7 +409,7 @@ func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeO } if !channel.ClientIsOperator(client) { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") return false } @@ -453,14 +454,14 @@ func (channel *Channel) Persist() (err error) { func (channel *Channel) Notice(client *Client, message string) { if !channel.CanSpeak(client) { - client.Send(nil, client.server.nameString, ERR_CANNOTSENDTOCHAN, channel.nameString, "Cannot send to channel") + client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return } for member := range channel.members { if member == client { continue } - member.SendFromClient(client, nil, client.nickMaskString, "NOTICE", channel.nameString, message) + member.SendFromClient(client, nil, client.nickMaskString, "NOTICE", channel.name, message) } } @@ -475,15 +476,15 @@ func (channel *Channel) Quit(client *Client) { func (channel *Channel) Kick(client *Client, target *Client, comment string) { if !(client.flags[Operator] || 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.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return } if !channel.ClientIsOperator(client) { - client.Send(nil, client.server.nameString, ERR_CANNOTSENDTOCHAN, channel.nameString, "Cannot send to channel") + client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return } if !channel.members.Has(target) { - client.Send(nil, client.server.nameString, ERR_USERNOTINCHANNEL, client.nickString, channel.nameString, "They aren't on that channel") + client.Send(nil, client.server.name, ERR_USERNOTINCHANNEL, client.nick, channel.name, "They aren't on that channel") return } @@ -492,19 +493,19 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string) { } for member := range channel.members { - member.Send(nil, client.nickMaskString, "KICK", channel.nameString, target.nickString, comment) + member.Send(nil, client.nickMaskString, "KICK", channel.name, target.nick, comment) } channel.Quit(target) } func (channel *Channel) Invite(invitee *Client, inviter *Client) { if channel.flags[InviteOnly] && !channel.ClientIsOperator(inviter) { - inviter.Send(nil, inviter.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") + inviter.Send(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") return } if !channel.members.Has(inviter) { - inviter.Send(nil, inviter.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel") + inviter.Send(nil, inviter.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return } @@ -513,10 +514,10 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) { channel.Persist() } - //TODO(dan): should inviter.server.nameString here be inviter.nickMaskString ? - inviter.Send(nil, inviter.server.nameString, RPL_INVITING, invitee.nickString, channel.nameString) - invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nickString, channel.nameString) + //TODO(dan): should inviter.server.name here be inviter.nickMaskString ? + inviter.Send(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name) + invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name) if invitee.flags[Away] { - inviter.Send(nil, inviter.server.nameString, RPL_AWAY, invitee.nickString, invitee.awayMessage) + inviter.Send(nil, inviter.server.name, RPL_AWAY, invitee.nick, invitee.awayMessage) } } diff --git a/irc/client.go b/irc/client.go index 7ad84be5..16ad4114 100644 --- a/irc/client.go +++ b/irc/client.go @@ -27,34 +27,35 @@ var ( ) type Client struct { - account *ClientAccount - atime time.Time - authorized bool - awayMessage string - capabilities CapabilitySet - capState CapState - certfp string - channels ChannelSet - ctime time.Time - flags map[UserMode]bool - isDestroyed bool - isQuitting bool - hasQuit bool - hops uint - hostname Name - idleTimer *time.Timer - nick Name - nickString string // cache for nick string since it's used with most numerics - nickMaskString string // cache for nickmask string since it's used with lots of replies - quitTimer *time.Timer - realname string - registered bool - saslInProgress bool - saslMechanism string - saslValue string - server *Server - socket *Socket - username Name + account *ClientAccount + atime time.Time + authorized bool + awayMessage string + capabilities CapabilitySet + capState CapState + certfp string + channels ChannelSet + ctime time.Time + flags map[UserMode]bool + isDestroyed bool + isQuitting bool + hasQuit bool + hops uint + hostname string + idleTimer *time.Timer + nick string + nickCasefolded string + nickMaskString string // cache for nickmask string since it's used with lots of replies + nickMaskCasefolded string + quitTimer *time.Timer + realname string + registered bool + saslInProgress bool + saslMechanism string + saslValue string + server *Server + socket *Socket + username string } func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { @@ -71,7 +72,8 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { server: server, socket: &socket, account: &NoAccount, - nickString: "*", // * is used until actual nick is given + nick: "*", // * is used until actual nick is given + nickCasefolded: "*", nickMaskString: "*", // * is used until actual nick is given } if isTLS { @@ -96,10 +98,10 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds) if err == nil { username := resp.Identifier - //TODO(dan): replace this with IsUsername/IsIRCName? - if Name(username).IsNickname() { + _, err := CasefoldName(username) // ensure it's a valid username + if err == nil { client.Notice("*** Found your username") - client.username = Name(username) + client.username = username // we don't need to updateNickMask here since nickMask is not used for anything yet } else { client.Notice("*** Got a malformed username, ignoring") @@ -144,7 +146,7 @@ func (client *Client) run() { cmd, exists := Commands[msg.Command] if !exists { - client.Send(nil, client.server.nameString, ERR_UNKNOWNCOMMAND, client.nickString, msg.Command, "Unknown command") + client.Send(nil, client.server.name, ERR_UNKNOWNCOMMAND, client.nick, msg.Command, "Unknown command") continue } @@ -196,7 +198,7 @@ func (client *Client) Touch() { } func (client *Client) Idle() { - client.Send(nil, "", "PING", client.nickString) + client.Send(nil, "", "PING", client.nick) if client.quitTimer == nil { client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout) @@ -226,11 +228,11 @@ func (client *Client) IdleSeconds() uint64 { } func (client *Client) HasNick() bool { - return client.nick != "" + return client.nick != "" && client.nick != "*" } func (client *Client) HasUsername() bool { - return client.username != "" + return client.username != "" && client.username != "*" } // @@ -244,29 +246,14 @@ func (c *Client) ModeString() (str string) { return } -func (c *Client) UserHost() Name { - username := "*" - if c.HasUsername() { - username = c.username.String() - } - return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname)) +func (c *Client) UserHost() string { + return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.hostname) } -func (c *Client) Nick() Name { - if c.HasNick() { - return c.nick - } - return Name("*") -} - -func (c *Client) Id() Name { +func (c *Client) Id() string { return c.UserHost() } -func (c *Client) String() string { - return c.Id().String() -} - // Friends refers to clients that share a channel with this client. func (client *Client) Friends() ClientSet { friends := make(ClientSet) @@ -280,30 +267,42 @@ func (client *Client) Friends() ClientSet { } func (client *Client) updateNickMask() { - client.nickString = client.nick.String() - client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nickString, client.username, client.hostname) + casefoldedName, err := CasefoldName(client.nick) + if err != nil { + log.Println(fmt.Sprintf("ERROR: Nick [%s] couldn't be casefolded... this should never happen.", client.nick)) + } + client.nickCasefolded = casefoldedName + + client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.hostname) + + nickMaskCasefolded, err := Casefold(client.nickMaskString) + if err != nil { + log.Println(fmt.Sprintf("ERROR: Nickmask [%s] couldn't be casefolded... this should never happen.", client.nickMaskString)) + } + client.nickMaskCasefolded = nickMaskCasefolded } -func (client *Client) SetNickname(nickname Name) { +func (client *Client) SetNickname(nickname string) { if client.HasNick() { - Log.error.Printf("%s nickname already set!", client) + Log.error.Printf("%s nickname already set!", client.nickMaskString) return } + fmt.Println("Setting nick to:", nickname, "from", client.nick) client.nick = nickname client.updateNickMask() client.server.clients.Add(client) } -func (client *Client) ChangeNickname(nickname Name) { +func (client *Client) ChangeNickname(nickname string) { origNickMask := client.nickMaskString client.server.clients.Remove(client) client.server.whoWas.Append(client) client.nick = nickname client.updateNickMask() client.server.clients.Add(client) - client.Send(nil, origNickMask, "NICK", nickname.String()) + client.Send(nil, origNickMask, "NICK", nickname) for friend := range client.Friends() { - friend.Send(nil, origNickMask, "NICK", nickname.String()) + friend.Send(nil, origNickMask, "NICK", nickname) } } @@ -381,7 +380,7 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm line, err := message.Line() if err != nil { // try not to fail quietly - especially useful when running tests, as a note to dig deeper - message = ircmsg.MakeMessage(nil, client.server.nameString, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") + message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") line, _ := message.Line() client.socket.Write(line) return err @@ -392,5 +391,5 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm // Notice sends the client a notice from the server. func (client *Client) Notice(text string) { - client.Send(nil, client.server.nameString, "NOTICE", client.nickString, text) + client.Send(nil, client.server.name, "NOTICE", client.nick, text) } diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go index b270601d..75df5f6c 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -1,10 +1,13 @@ // Copyright (c) 2012-2014 Jeremy Latt +// Copyright (c) 2016- Daniel Oaks // released under the MIT license package irc import ( "errors" + "fmt" + "log" "regexp" "strings" @@ -17,31 +20,35 @@ var ( ErrNicknameMismatch = errors.New("nickname mismatch") ) -func ExpandUserHost(userhost Name) (expanded Name) { +func ExpandUserHost(userhost string) (expanded string) { expanded = userhost // fill in missing wildcards for nicks //TODO(dan): this would fail with dan@lol, do we want to accommodate that? - if !strings.Contains(expanded.String(), "!") { + if !strings.Contains(expanded, "!") { expanded += "!*" } - if !strings.Contains(expanded.String(), "@") { + if !strings.Contains(expanded, "@") { expanded += "@*" } return } type ClientLookupSet struct { - byNick map[Name]*Client + byNick map[string]*Client } func NewClientLookupSet() *ClientLookupSet { return &ClientLookupSet{ - byNick: make(map[Name]*Client), + byNick: make(map[string]*Client), } } -func (clients *ClientLookupSet) Get(nick Name) *Client { - return clients.byNick[nick.ToLower()] +func (clients *ClientLookupSet) Get(nick string) *Client { + casefoldedName, err := CasefoldName(nick) + if err == nil { + return clients.byNick[casefoldedName] + } + return nil } func (clients *ClientLookupSet) Add(client *Client) error { @@ -51,7 +58,7 @@ func (clients *ClientLookupSet) Add(client *Client) error { if clients.Get(client.nick) != nil { return ErrNicknameInUse } - clients.byNick[client.Nick().ToLower()] = client + clients.byNick[client.nickCasefolded] = client return nil } @@ -62,20 +69,21 @@ func (clients *ClientLookupSet) Remove(client *Client) error { if clients.Get(client.nick) != client { return ErrNicknameMismatch } - delete(clients.byNick, client.nick.ToLower()) + delete(clients.byNick, client.nickCasefolded) return nil } -func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) { +func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) { set = make(ClientSet) - userhost = ExpandUserHost(userhost) - matcher := ircmatch.MakeMatch(userhost.String()) + userhost, err := Casefold(ExpandUserHost(userhost)) + if err != nil { + return set + } + matcher := ircmatch.MakeMatch(userhost) - var casemappedNickMask string for _, client := range clients.byNick { - casemappedNickMask = NewName(client.nickMaskString).String() - if matcher.Match(casemappedNickMask) { + if matcher.Match(client.nickMaskCasefolded) { set.Add(client) } } @@ -83,14 +91,15 @@ func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) { return set } -func (clients *ClientLookupSet) Find(userhost Name) *Client { - userhost = ExpandUserHost(userhost) - matcher := ircmatch.MakeMatch(userhost.String()) +func (clients *ClientLookupSet) Find(userhost string) *Client { + userhost, err := Casefold(ExpandUserHost(userhost)) + if err != nil { + return nil + } + matcher := ircmatch.MakeMatch(userhost) - var casemappedNickMask string for _, client := range clients.byNick { - casemappedNickMask = NewName(client.nickMaskString).String() - if matcher.Match(casemappedNickMask) { + if matcher.Match(client.nickMaskCasefolded) { return client } } @@ -105,26 +114,31 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client { //TODO(dan): move this over to generally using glob syntax instead? // kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?) type UserMaskSet struct { - masks map[Name]bool + masks map[string]bool regexp *regexp.Regexp } func NewUserMaskSet() *UserMaskSet { return &UserMaskSet{ - masks: make(map[Name]bool), + masks: make(map[string]bool), } } -func (set *UserMaskSet) Add(mask Name) bool { - if set.masks[mask] { +func (set *UserMaskSet) Add(mask string) bool { + casefoldedMask, err := Casefold(mask) + if err != nil { + log.Println(fmt.Sprintf("ERROR: Could not add mask to usermaskset: [%s]", mask)) return false } - set.masks[mask] = true + if set.masks[casefoldedMask] { + return false + } + set.masks[casefoldedMask] = true set.setRegexp() return true } -func (set *UserMaskSet) AddAll(masks []Name) (added bool) { +func (set *UserMaskSet) AddAll(masks []string) (added bool) { for _, mask := range masks { if !added && !set.masks[mask] { added = true @@ -135,7 +149,7 @@ func (set *UserMaskSet) AddAll(masks []Name) (added bool) { return } -func (set *UserMaskSet) Remove(mask Name) bool { +func (set *UserMaskSet) Remove(mask string) bool { if !set.masks[mask] { return false } @@ -144,18 +158,18 @@ func (set *UserMaskSet) Remove(mask Name) bool { return true } -func (set *UserMaskSet) Match(userhost Name) bool { +func (set *UserMaskSet) Match(userhost string) bool { if set.regexp == nil { return false } - return set.regexp.MatchString(userhost.String()) + return set.regexp.MatchString(userhost) } func (set *UserMaskSet) String() string { masks := make([]string, len(set.masks)) index := 0 for mask := range set.masks { - masks[index] = mask.String() + masks[index] = mask index += 1 } return strings.Join(masks, " ") @@ -176,7 +190,7 @@ func (set *UserMaskSet) setRegexp() { maskExprs := make([]string, len(set.masks)) index := 0 for mask := range set.masks { - manyParts := strings.Split(mask.String(), "*") + manyParts := strings.Split(mask, "*") manyExprs := make([]string, len(manyParts)) for mindex, manyPart := range manyParts { oneParts := strings.Split(manyPart, "?") diff --git a/irc/commands.go b/irc/commands.go index a5bb2592..66e1ff79 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -24,11 +24,11 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b return false } if cmd.oper && !client.flags[Operator] { - client.Send(nil, server.nameString, ERR_NOPRIVILEGES, client.nickString, "Permission Denied - You're not an IRC operator") + client.Send(nil, server.name, ERR_NOPRIVILEGES, client.nick, "Permission Denied - You're not an IRC operator") return false } if len(msg.Params) < cmd.minParams { - client.Send(nil, server.nameString, ERR_NEEDMOREPARAMS, client.nickString, msg.Command, "Not enough parameters") + client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters") return false } if !cmd.leaveClientActive { diff --git a/irc/config.go b/irc/config.go index 74110317..9b445cc8 100644 --- a/irc/config.go +++ b/irc/config.go @@ -103,22 +103,32 @@ type Config struct { } } -func (conf *Config) Operators() map[Name][]byte { - operators := make(map[Name][]byte) +func (conf *Config) Operators() map[string][]byte { + operators := make(map[string][]byte) for name, opConf := range conf.Operator { - operators[NewName(name)] = opConf.PasswordBytes() + name, err := CasefoldName(name) + if err == nil { + operators[name] = opConf.PasswordBytes() + } else { + log.Println("Could not casefold oper name:", err.Error()) + } } return operators } -func (conf *Config) TLSListeners() map[Name]*tls.Config { - tlsListeners := make(map[Name]*tls.Config) +func (conf *Config) TLSListeners() map[string]*tls.Config { + tlsListeners := make(map[string]*tls.Config) for s, tlsListenersConf := range conf.Server.TLSListeners { config, err := tlsListenersConf.Config() if err != nil { log.Fatal(err) } - tlsListeners[NewName(s)] = config + name, err := CasefoldName(s) + if err == nil { + tlsListeners[name] = config + } else { + log.Println("Could not casefold TLS listener:", err.Error()) + } } return tlsListeners } diff --git a/irc/isupport.go b/irc/isupport.go index 852a7a9c..36ff5aaa 100644 --- a/irc/isupport.go +++ b/irc/isupport.go @@ -70,6 +70,6 @@ func (il *ISupportList) RegenerateCachedReply() { func (client *Client) RplISupport() { for _, tokenline := range client.server.isupport.CachedReply { // ugly trickery ahead - client.Send(nil, client.server.nameString, RPL_ISUPPORT, append([]string{client.nickString}, tokenline...)...) + client.Send(nil, client.server.name, RPL_ISUPPORT, append([]string{client.nick}, tokenline...)...) } } diff --git a/irc/modes.go b/irc/modes.go index 6ff8a2f1..355fc436 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -120,7 +120,7 @@ func (changes ChannelModeChanges) String() string { } type ChannelModeCommand struct { - channel Name + channel string changes ChannelModeChanges } @@ -209,8 +209,9 @@ var ( // MODE [ [...]] func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - name := NewName(msg.Params[0]) - if name.IsChannel() { + _, errChan := CasefoldChannel(msg.Params[0]) + + if errChan != nil { return cmodeHandler(server, client, msg) } else { return umodeHandler(server, client, msg) @@ -219,12 +220,12 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // MODE [ [...]] func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - nickname := NewName(msg.Params[0]) + nickname, err := CasefoldName(msg.Params[0]) target := server.clients.Get(nickname) - if target == nil { - client.Send(nil, server.nameString, ERR_NOSUCHNICK, client.nickString, msg.Params[0], "No such nick") + if err != nil || target == nil { + client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], "No such nick") return false } @@ -232,9 +233,9 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // point SAMODE at this handler too, if they are operator and SAMODE was called then fine if client != target && !client.flags[Operator] { if len(msg.Params) > 1 { - client.Send(nil, server.nameString, ERR_USERSDONTMATCH, client.nickString, "Can't change modes for other users") + client.Send(nil, server.name, ERR_USERSDONTMATCH, client.nick, "Can't change modes for other users") } else { - client.Send(nil, server.nameString, ERR_USERSDONTMATCH, client.nickString, "Can't view modes for other users") + client.Send(nil, server.name, ERR_USERSDONTMATCH, client.nick, "Can't view modes for other users") } return false } @@ -249,7 +250,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if (op == Add) || (op == Remove) { modeArg = modeArg[1:] } else { - client.Send(nil, server.nameString, ERR_UNKNOWNMODE, client.nickString, string(modeArg[0]), "is an unknown mode character to me") + client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me") return false } @@ -298,20 +299,20 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } if len(applied) > 0 { - client.Send(nil, client.nickMaskString, "MODE", target.nickString, applied.String()) + client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String()) } else if client == target { - client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nickString, target.ModeString()) + client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nick, target.ModeString()) } return false } // MODE [ [...]] func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - channelName := NewName(msg.Params[0]) + channelName, err := CasefoldChannel(msg.Params[0]) channel := server.channels.Get(channelName) - if channel == nil { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, msg.Params[0], "No such channel") + if err != nil || channel == nil { + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], "No such channel") return false } @@ -327,7 +328,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if (op == Add) || (op == Remove) { modeArg = modeArg[1:] } else { - client.Send(nil, server.nameString, ERR_UNKNOWNMODE, client.nickString, string(modeArg[0]), "is an unknown mode character to me") + client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me") return false } @@ -380,7 +381,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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") + client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "MODE", "Could not complete MODE command") return false } @@ -389,13 +390,19 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { continue } + // confirm mask looks valid + mask, err = Casefold(mask) + if err != nil { + continue + } + switch change.op { case Add: - list.Add(Name(mask)) + list.Add(mask) applied = append(applied, change) case Remove: - list.Remove(Name(mask)) + list.Remove(mask) applied = append(applied, change) } @@ -460,13 +467,16 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } } - name := NewName(change.arg) + casefoldedName, err := CasefoldName(change.arg) + if err != nil { + continue + } if !hasPrivs { - if change.op == Remove && name.ToLower() == client.nick.ToLower() { + if change.op == Remove && casefoldedName == client.nickCasefolded { // success! } else { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, channel.nameString, "You're not a channel operator") + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") continue } } @@ -481,13 +491,13 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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(), " ")...) + args := append([]string{channel.name}, 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), " ")...) + args := append([]string{client.nick, channel.name}, strings.Split(channel.ModeString(client), " ")...) client.Send(nil, client.nickMaskString, RPL_CHANNELMODEIS, args...) - client.Send(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nickString, channel.nameString, strconv.FormatInt(channel.createdTime.Unix(), 10)) + client.Send(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10)) } return false } diff --git a/irc/net.go b/irc/net.go index c1050121..f291a347 100644 --- a/irc/net.go +++ b/irc/net.go @@ -9,27 +9,27 @@ import ( "strings" ) -func IPString(addr net.Addr) Name { +func IPString(addr net.Addr) string { addrStr := addr.String() ipaddr, _, err := net.SplitHostPort(addrStr) if err != nil { - return Name(addrStr) + return addrStr } - return Name(ipaddr) + return ipaddr } -func AddrLookupHostname(addr net.Addr) Name { +func AddrLookupHostname(addr net.Addr) string { return LookupHostname(IPString(addr)) } -func LookupHostname(addr Name) Name { - names, err := net.LookupAddr(addr.String()) - if err != nil { - return Name(addr) +func LookupHostname(addr string) string { + names, err := net.LookupAddr(addr) + if err != nil || !IsHostname(names[0]) { + // return original address + return addr } - hostname := strings.TrimSuffix(names[0], ".") - return Name(hostname) + return names[0] } var allowedHostnameChars = "abcdefghijklmnopqrstuvwxyz1234567890-." diff --git a/irc/nickname.go b/irc/nickname.go index 549db71e..1d90caec 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -4,7 +4,11 @@ package irc -import "github.com/DanielOaks/girc-go/ircmsg" +import ( + "strings" + + "github.com/DanielOaks/girc-go/ircmsg" +) // NICK func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { @@ -13,15 +17,15 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return true } - nickname := NewName(msg.Params[0]) + nickname, err := CasefoldName(msg.Params[0]) - if len(nickname) < 1 { - client.Send(nil, server.nameString, ERR_NONICKNAMEGIVEN, client.nickString, "No nickname given") + if len(strings.TrimSpace(msg.Params[0])) < 1 { + client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given") return false } - if !nickname.IsNickname() { - client.Send(nil, server.nameString, ERR_ERRONEUSNICKNAME, client.nickString, msg.Params[0], "Erroneous nickname") + if err != nil || len(strings.TrimSpace(msg.Params[0])) > server.limits.NickLen { + client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname") return false } @@ -32,7 +36,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { //TODO(dan): There's probably some races here, we should be changing this in the primary server thread target := server.clients.Get(nickname) if target != nil && target != client { - client.Send(nil, server.nameString, ERR_NICKNAMEINUSE, client.nickString, msg.Params[0], "Nickname is already in use") + client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use") return false } @@ -52,35 +56,35 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return true } - oldnick := NewName(msg.Params[0]) - nickname := NewName(msg.Params[1]) + oldnick, oerr := CasefoldName(msg.Params[0]) + casefoldedNickname, err := CasefoldName(msg.Params[1]) - if len(nickname) < 1 { - client.Send(nil, server.nameString, ERR_NONICKNAMEGIVEN, client.nickString, "No nickname given") + if len(casefoldedNickname) < 1 { + client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given") return false } - if !nickname.IsNickname() { - client.Send(nil, server.nameString, ERR_ERRONEUSNICKNAME, client.nickString, msg.Params[0], "Erroneous nickname") + if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen { + client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname") return false } - if client.nick == nickname { + if client.nick == msg.Params[1] { return false } target := server.clients.Get(oldnick) if target == nil { - client.Send(nil, server.nameString, ERR_NOSUCHNICK, msg.Params[0], "No such nick") + client.Send(nil, server.name, ERR_NOSUCHNICK, msg.Params[0], "No such nick") return false } //TODO(dan): There's probably some races here, we should be changing this in the primary server thread - if server.clients.Get(nickname) != nil { - client.Send(nil, server.nameString, ERR_NICKNAMEINUSE, client.nickString, msg.Params[0], "Nickname is already in use") + if server.clients.Get(casefoldedNickname) != nil || server.clients.Get(casefoldedNickname) != target { + client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use") return false } - target.ChangeNickname(nickname) + target.ChangeNickname(msg.Params[1]) return false } diff --git a/irc/registration.go b/irc/registration.go index 4a33f4ad..2667a3d5 100644 --- a/irc/registration.go +++ b/irc/registration.go @@ -73,7 +73,7 @@ func regHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } else if subcommand == "verify" { client.Notice("Parsing VERIFY") } else { - client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", msg.Params[0], "Unknown subcommand") + client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", msg.Params[0], "Unknown subcommand") } return false @@ -94,30 +94,30 @@ func removeFailedRegCreateData(store buntdb.DB, account string) { // regCreateHandler parses the REG CREATE command. func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // get and sanitise account name - account := NewName(msg.Params[1]) - //TODO(dan): probably don't need explicit check for "*" here... until we actually casemap properly as per rfc7700 - if !account.IsNickname() || msg.Params[1] == "*" { - client.Send(nil, server.nameString, ERR_REG_UNSPECIFIED_ERROR, client.nickString, msg.Params[1], "Account name is not valid") + account := strings.TrimSpace(msg.Params[1]) + casefoldedAccount, err := CasefoldName(account) + // probably don't need explicit check for "*" here... but let's do it anyway just to make sure + if err != nil || msg.Params[1] == "*" { + client.Send(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, account, "Account name is not valid") return false } - accountString := account.String() // check whether account exists // do it all in one write tx to prevent races - err := server.store.Update(func(tx *buntdb.Tx) error { - accountKey := fmt.Sprintf(keyAccountExists, accountString) + err = server.store.Update(func(tx *buntdb.Tx) error { + accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) _, err := tx.Get(accountKey) if err != buntdb.ErrNotFound { //TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be - client.Send(nil, server.nameString, ERR_ACCOUNT_ALREADY_EXISTS, client.nickString, msg.Params[1], "Account already exists") + client.Send(nil, server.name, ERR_ACCOUNT_ALREADY_EXISTS, client.nick, account, "Account already exists") return errAccountCreation } - registeredTimeKey := fmt.Sprintf(keyAccountRegTime, accountString) + registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount) tx.Set(accountKey, "1", nil) - tx.Set(fmt.Sprintf(keyAccountName, accountString), strings.TrimSpace(msg.Params[1]), nil) + tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil) tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil) return nil }) @@ -125,7 +125,7 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo // account could not be created and relevant numerics have been dispatched, abort if err != nil { if err != errAccountCreation { - client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", "Could not register") + client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", "CREATE", "Could not register") log.Println("Could not save registration initial data:", err.Error()) } return false @@ -155,8 +155,8 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo } if !callbackValid { - client.Send(nil, server.nameString, ERR_REG_INVALID_CALLBACK, client.nickString, msg.Params[1], callbackNamespace, "Callback namespace is not supported") - removeFailedRegCreateData(server.store, accountString) + client.Send(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, "Callback namespace is not supported") + removeFailedRegCreateData(server.store, casefoldedAccount) return false } @@ -170,8 +170,8 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo credentialType = "passphrase" // default from the spec credentialValue = msg.Params[3] } else { - client.Send(nil, server.nameString, ERR_NEEDMOREPARAMS, client.nickString, msg.Command, "Not enough parameters") - removeFailedRegCreateData(server.store, accountString) + client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters") + removeFailedRegCreateData(server.store, casefoldedAccount) return false } @@ -183,14 +183,14 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo } } if credentialType == "certfp" && client.certfp == "" { - client.Send(nil, server.nameString, ERR_REG_INVALID_CRED_TYPE, client.nickString, credentialType, callbackNamespace, "You are not using a certificiate") - removeFailedRegCreateData(server.store, accountString) + client.Send(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, "You are not using a certificiate") + removeFailedRegCreateData(server.store, casefoldedAccount) return false } if !credentialValid { - client.Send(nil, server.nameString, ERR_REG_INVALID_CRED_TYPE, client.nickString, credentialType, callbackNamespace, "Credential type is not supported") - removeFailedRegCreateData(server.store, accountString) + client.Send(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, "Credential type is not supported") + removeFailedRegCreateData(server.store, casefoldedAccount) return false } @@ -206,7 +206,7 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo return errCertfpAlreadyExists } - tx.Set(assembledKeyCertToAccount, account.String(), nil) + tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil) } // make creds @@ -241,9 +241,9 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo if err == errCertfpAlreadyExists { errMsg = "An account already exists for your certificate fingerprint" } - client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", errMsg) + client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", "CREATE", errMsg) log.Println("Could not save registration creds:", err.Error()) - removeFailedRegCreateData(server.store, accountString) + removeFailedRegCreateData(server.store, casefoldedAccount) return false } @@ -259,18 +259,18 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo Clients: []*Client{client}, } //TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables - server.accounts[accountString] = &account + server.accounts[casefoldedAccount] = &account client.account = &account - client.Send(nil, server.nameString, RPL_REGISTRATION_SUCCESS, client.nickString, accountString, "Account created") - client.Send(nil, server.nameString, RPL_LOGGEDIN, client.nickString, client.nickMaskString, accountString, fmt.Sprintf("You are now logged in as %s", accountString)) - client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "Authentication successful") + client.Send(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, account.Name, "Account created") + client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf("You are now logged in as %s", account.Name)) + client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "Authentication successful") return nil }) if err != nil { - client.Send(nil, server.nameString, ERR_UNKNOWNERROR, client.nickString, "REG", "CREATE", "Could not register") + client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "REG", "CREATE", "Could not register") log.Println("Could not save verification confirmation (*):", err.Error()) - removeFailedRegCreateData(server.store, accountString) + removeFailedRegCreateData(server.store, casefoldedAccount) return false } diff --git a/irc/server.go b/irc/server.go index dd8fd6ed..d65f1e73 100644 --- a/irc/server.go +++ b/irc/server.go @@ -15,7 +15,6 @@ import ( "net/http" "os" "os/signal" - "regexp" "strconv" "strings" "syscall" @@ -27,9 +26,11 @@ import ( // Limits holds the maximum limits for various things such as topic lengths type Limits struct { - AwayLen int - KickLen int - TopicLen int + AwayLen int + ChannelLen int + KickLen int + NickLen int + TopicLen int } type Server struct { @@ -42,10 +43,10 @@ type Server struct { idle chan *Client limits Limits motdLines []string - name Name - nameString string // cache for server name string since it's used with almost every reply + name string + nameCasefolded string newConns chan clientConn - operators map[Name][]byte + operators map[string][]byte password []byte passwords *PasswordManager accountRegistration *AccountRegistration @@ -71,6 +72,12 @@ type clientConn struct { } func NewServer(config *Config) *Server { + casefoldedName, err := Casefold(config.Server.Name) + if err != nil { + log.Println(fmt.Sprintf("Server name isn't valid: []", config.Server.Name), err.Error()) + return nil + } + server := &Server{ accounts: make(map[string]*ClientAccount), channels: make(ChannelNameMap), @@ -79,12 +86,14 @@ func NewServer(config *Config) *Server { ctime: time.Now(), idle: make(chan *Client), limits: Limits{ - AwayLen: config.Limits.AwayLen, - KickLen: config.Limits.KickLen, - TopicLen: config.Limits.TopicLen, + AwayLen: config.Limits.AwayLen, + ChannelLen: config.Limits.ChannelLen, + KickLen: config.Limits.KickLen, + NickLen: config.Limits.NickLen, + TopicLen: config.Limits.TopicLen, }, - name: NewName(config.Server.Name), - nameString: NewName(config.Server.Name).String(), + name: config.Server.Name, + nameCasefolded: casefoldedName, newConns: make(chan clientConn), operators: config.Operators(), signals: make(chan os.Signal, len(SERVER_SIGNALS)), @@ -141,10 +150,6 @@ func NewServer(config *Config) *Server { } } - //TODO(dan): Hot damn this is an ugly hack. Fix it properly at some point. - ChannelNameExpr = regexp.MustCompile(fmt.Sprintf(`^[#][\pL\pN\pP\pS]{1,%d}$`, config.Limits.ChannelLen)) - NicknameExpr = regexp.MustCompile(fmt.Sprintf("^[\\pL\\pN\\pP\\pS]{1,%d}$", config.Limits.NickLen)) - if config.Server.Password != "" { server.password = config.Server.PasswordBytes() } @@ -169,7 +174,7 @@ func NewServer(config *Config) *Server { // add RPL_ISUPPORT tokens server.isupport = NewISupportList() server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen)) - server.isupport.Add("CASEMAPPING", "ascii") + server.isupport.Add("CASEMAPPING", "rfc7700") server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, Persistent, ReOp, Secret}.String()}, ",")) server.isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen)) server.isupport.Add("CHANTYPES", "#") @@ -209,7 +214,7 @@ func loadChannelList(channel *Channel, list string, maskMode ChannelMode) { if list == "" { return } - channel.lists[maskMode].AddAll(NewNames(strings.Split(list, " "))) + channel.lists[maskMode].AddAll(strings.Split(list, " ")) } func (server *Server) loadChannels() { @@ -287,8 +292,9 @@ func (server *Server) Run() { // listen goroutine // -func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) { - config, listenTLS := tlsMap[NewName(addr)] +func (s *Server) listen(addr string, tlsMap map[string]*tls.Config) { + //TODO(dan): we could casemap this but... eh + config, listenTLS := tlsMap[addr] listener, err := net.Listen("tcp", addr) if err != nil { @@ -301,16 +307,16 @@ func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) { listener = tls.NewListener(listener, config) tlsString = "TLS" } - Log.info.Printf("%s listening on %s using %s.", s, addr, tlsString) + Log.info.Printf("%s listening on %s using %s.", s.name, addr, tlsString) go func() { for { conn, err := listener.Accept() if err != nil { - Log.error.Printf("%s accept error: %s", s, err) + Log.error.Printf("%s accept error: %s", s.name, err) continue } - Log.debug.Printf("%s accept: %s", s, conn.RemoteAddr()) + Log.debug.Printf("%s accept: %s", s.name, conn.RemoteAddr()) newConn := clientConn{ Conn: conn, @@ -329,7 +335,7 @@ func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) { func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { - Log.error.Printf("%s method not allowed", s) + Log.error.Printf("%s method not allowed", s.name) return } @@ -342,7 +348,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - Log.error.Printf("%s websocket upgrade error: %s", s, err) + Log.error.Printf("%s websocket upgrade error: %s", s.name, err) return } @@ -360,7 +366,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) { if listenTLS { tlsString = "TLS" } - Log.info.Printf("%s websocket listening on %s using %s.", s, addr, tlsString) + Log.info.Printf("%s websocket listening on %s using %s.", s.name, addr, tlsString) if listenTLS { err = http.ListenAndServeTLS(addr, config.Cert, config.Key, nil) @@ -368,7 +374,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) { err = http.ListenAndServe(addr, nil) } if err != nil { - Log.error.Printf("%s listenAndServe (%s) error: %s", s, tlsString, err) + Log.error.Printf("%s listenAndServe (%s) error: %s", s.name, tlsString, err) } }() } @@ -387,38 +393,34 @@ func (s *Server) tryRegister(c *Client) { // send welcome text //NOTE(dan): we specifically use the NICK here instead of the nickmask // see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask - c.Send(nil, s.nameString, RPL_WELCOME, c.nickString, fmt.Sprintf("Welcome to the Internet Relay Network %s", c.nickString)) - c.Send(nil, s.nameString, RPL_YOURHOST, c.nickString, fmt.Sprintf("Your host is %s, running version %s", s.nameString, VER)) - c.Send(nil, s.nameString, RPL_CREATED, c.nickString, fmt.Sprintf("This server was created %s", s.ctime.Format(time.RFC1123))) + c.Send(nil, s.name, RPL_WELCOME, c.nick, fmt.Sprintf("Welcome to the Internet Relay Network %s", c.nick)) + c.Send(nil, s.name, RPL_YOURHOST, c.nick, fmt.Sprintf("Your host is %s, running version %s", s.name, VER)) + c.Send(nil, s.name, RPL_CREATED, c.nick, fmt.Sprintf("This server was created %s", s.ctime.Format(time.RFC1123))) //TODO(dan): Look at adding last optional [] parameter - c.Send(nil, s.nameString, RPL_MYINFO, c.nickString, s.nameString, VER, supportedUserModesString, supportedChannelModesString) + c.Send(nil, s.name, RPL_MYINFO, c.nick, s.name, VER, supportedUserModesString, supportedChannelModesString) c.RplISupport() s.MOTD(c) - c.Send(nil, c.nickMaskString, RPL_UMODEIS, c.nickString, c.ModeString()) + c.Send(nil, c.nickMaskString, RPL_UMODEIS, c.nick, c.ModeString()) } func (server *Server) MOTD(client *Client) { if len(server.motdLines) < 1 { - client.Send(nil, server.nameString, ERR_NOMOTD, client.nickString, "MOTD File is missing") + client.Send(nil, server.name, ERR_NOMOTD, client.nick, "MOTD File is missing") return } - client.Send(nil, server.nameString, RPL_MOTDSTART, client.nickString, fmt.Sprintf("- %s Message of the day - ", server.nameString)) + client.Send(nil, server.name, RPL_MOTDSTART, client.nick, fmt.Sprintf("- %s Message of the day - ", server.name)) for _, line := range server.motdLines { - client.Send(nil, server.nameString, RPL_MOTD, client.nickString, line) + client.Send(nil, server.name, RPL_MOTD, client.nick, line) } - client.Send(nil, server.nameString, RPL_ENDOFMOTD, client.nickString, "End of MOTD command") + client.Send(nil, server.name, RPL_ENDOFMOTD, client.nick, "End of MOTD command") } -func (s *Server) Id() Name { +func (s *Server) Id() string { return s.name } -func (s *Server) String() string { - return s.name.String() -} - -func (s *Server) Nick() Name { +func (s *Server) Nick() string { return s.Id() } @@ -429,7 +431,7 @@ func (s *Server) Nick() Name { // PASS func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if client.registered { - client.Send(nil, server.nameString, ERR_ALREADYREGISTRED, client.nickString, "You may not reregister") + client.Send(nil, server.name, ERR_ALREADYREGISTRED, client.nick, "You may not reregister") return false } @@ -442,8 +444,8 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // check the provided password password := []byte(msg.Params[0]) if ComparePassword(server.password, password) != nil { - client.Send(nil, server.nameString, ERR_PASSWDMISMATCH, client.nickString, "Password incorrect") - client.Send(nil, server.nameString, "ERROR", "Password incorrect") + client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect") + client.Send(nil, server.name, "ERROR", "Password incorrect") return true } @@ -454,12 +456,12 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - clientAddress := IPString(client.socket.conn.RemoteAddr()).String() - clientHostname := client.hostname.String() + clientAddress := IPString(client.socket.conn.RemoteAddr()) + clientHostname := client.hostname for _, address := range server.proxyAllowedFrom { if clientHostname == address || clientAddress == address { - client.hostname = LookupHostname(NewName(msg.Params[1])) + client.hostname = LookupHostname(msg.Params[1]) return false } } @@ -471,7 +473,7 @@ func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // USER * 0 func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if client.registered { - client.Send(nil, server.nameString, ERR_ALREADYREGISTRED, client.nickString, "You may not reregister") + client.Send(nil, server.name, ERR_ALREADYREGISTRED, client.nick, "You may not reregister") return false } @@ -486,7 +488,8 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // confirm that username is valid // - if !Name(msg.Params[0]).IsNickname() { + _, err := CasefoldName(msg.Params[0]) + if err != nil { client.Send(nil, "", "ERROR", "Malformed username") return true } @@ -499,7 +502,7 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { server.clients.Remove(client) if !client.HasUsername() { - client.username = Name("~" + msg.Params[0]) + client.username = "~" + msg.Params[0] client.updateNickMask() } if client.realname == "" { @@ -528,7 +531,7 @@ func quitHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // PING [] func pingHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - client.Send(nil, server.nameString, "PONG", msg.Params...) + client.Send(nil, server.name, "PONG", msg.Params...) return false } @@ -544,7 +547,7 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // handle JOIN 0 if msg.Params[0] == "0" { for channel := range client.channels { - channel.Part(client, client.nickString) + channel.Part(client, client.nickCasefolded) } return false } @@ -556,16 +559,15 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { keys = strings.Split(msg.Params[1], ",") } - var name Name - for i, nameString := range channels { - name = Name(nameString) - if !name.IsChannel() { - fmt.Println("ISN'T CHANNEL NAME:", nameString) - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, nameString, "No such channel") + for i, name := range channels { + casefoldedName, err := CasefoldChannel(name) + if err != nil { + log.Println("ISN'T CHANNEL NAME:", name) + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, name, "No such channel") continue } - channel := server.channels.Get(name) + channel := server.channels.Get(casefoldedName) if channel == nil { channel = NewChannel(server, name, true) } @@ -589,10 +591,11 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } for _, chname := range channels { - channel := server.channels.Get(Name(chname)) + casefoldedChannelName, err := CasefoldChannel(chname) + channel := server.channels.Get(casefoldedChannelName) - if channel == nil { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel") + if err != nil || channel == nil { + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel") continue } @@ -603,9 +606,10 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // TOPIC [] func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - channel := server.channels.Get(Name(msg.Params[0])) - if channel == nil { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, msg.Params[0], "No such channel") + name, err := CasefoldChannel(msg.Params[0]) + channel := server.channels.Get(name) + if err != nil || channel == nil { + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], "No such channel") return false } @@ -622,26 +626,26 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool targets := strings.Split(msg.Params[0], ",") message := msg.Params[1] - var target Name for _, targetString := range targets { - target = Name(targetString) - if target.IsChannel() { + target, err := CasefoldChannel(targetString) + if err != nil { channel := server.channels.Get(target) if channel == nil { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, targetString, "No such channel") + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel") continue } channel.PrivMsg(client, message) } else { + target, err = CasefoldName(targetString) user := server.clients.Get(target) - if user == nil { - client.Send(nil, server.nameString, ERR_NOSUCHNICK, targetString, "No such nick") + if err != nil || user == nil { + client.Send(nil, server.name, ERR_NOSUCHNICK, target, "No such nick") continue } - user.Send(nil, client.nickMaskString, "PRIVMSG", user.nickString, message) + user.Send(nil, client.nickMaskString, "PRIVMSG", user.nick, message) if user.flags[Away] { //TODO(dan): possibly implement cooldown of away notifications to users - client.Send(nil, server.nameString, RPL_AWAY, user.nickString, user.awayMessage) + client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage) } } } @@ -657,7 +661,7 @@ func (client *Client) WhoisChannelsNames(target *Client) []string { if !target.flags[Operator] && channel.flags[Secret] && !channel.members.Has(target) { continue } - chstrs = append(chstrs, channel.members[client].Prefixes(isMultiPrefix)+channel.name.String()) + chstrs = append(chstrs, channel.members[client].Prefixes(isMultiPrefix)+channel.name) index++ } return chstrs @@ -678,9 +682,14 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if client.flags[Operator] { masks := strings.Split(masksString, ",") for _, mask := range masks { - matches := server.clients.FindAll(Name(mask)) + casefoldedMask, err := Casefold(mask) + if err != nil { + client.Send(nil, client.server.name, ERR_NOSUCHNICK, mask, "No such nick") + continue + } + matches := server.clients.FindAll(casefoldedMask) if len(matches) == 0 { - client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, mask, "No such nick") + client.Send(nil, client.server.name, ERR_NOSUCHNICK, mask, "No such nick") continue } for mclient := range matches { @@ -690,31 +699,32 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } else { // specifically treat this as a single lookup rather than splitting as we do above // this is by design - mclient := server.clients.Get(Name(masksString)) - if mclient == nil { - client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, masksString, "No such nick") + casefoldedMask, err := Casefold(masksString) + mclient := server.clients.Get(casefoldedMask) + if err != nil || mclient == nil { + client.Send(nil, client.server.name, ERR_NOSUCHNICK, masksString, "No such nick") // fall through, ENDOFWHOIS is always sent } else { client.getWhoisOf(mclient) } } - client.Send(nil, server.nameString, RPL_ENDOFWHOIS, client.nickString, masksString, "End of /WHOIS list") + client.Send(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, "End of /WHOIS list") return false } func (client *Client) getWhoisOf(target *Client) { - client.Send(nil, client.server.nameString, RPL_WHOISUSER, client.nickString, target.nickString, target.username.String(), target.hostname.String(), "*", target.realname) + client.Send(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname) //TODO(dan): ...one channel per reply? really? for _, line := range client.WhoisChannelsNames(target) { - client.Send(nil, client.server.nameString, RPL_WHOISCHANNELS, client.nickString, target.nickString, line) + client.Send(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, line) } if target.flags[Operator] { - client.Send(nil, client.server.nameString, RPL_WHOISOPERATOR, client.nickString, target.nickString, "is an IRC operator") + client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, "is an IRC operator") } if target.certfp != "" && (client.flags[Operator] || client == target) { - client.Send(nil, client.server.nameString, RPL_WHOISCERTFP, client.nickString, target.nickString, fmt.Sprintf("has client certificate fingerprint %s", target.certfp)) + client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf("has client certificate fingerprint %s", target.certfp)) } - client.Send(nil, client.server.nameString, RPL_WHOISIDLE, client.nickString, target.nickString, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time") + client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time") } // ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ] @@ -734,9 +744,9 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) { if channel != nil { flags += channel.members[client].Prefixes(target.capabilities[MultiPrefix]) - channelName = channel.name.String() + channelName = channel.name } - target.Send(nil, target.server.nameString, RPL_WHOREPLY, target.nickString, channelName, client.username.String(), client.hostname.String(), client.server.nameString, client.nickString, flags, string(client.hops), client.realname) + target.Send(nil, target.server.name, RPL_WHOREPLY, target.nick, channelName, client.username, client.hostname, client.server.name, client.nick, flags, string(client.hops), client.realname) } func whoChannel(client *Client, channel *Channel, friends ClientSet) { @@ -751,9 +761,14 @@ func whoChannel(client *Client, channel *Channel, friends ClientSet) { func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { friends := client.Friends() - var mask Name + var mask string if len(msg.Params) > 0 { - mask = Name(msg.Params[0]) + casefoldedMask, err := Casefold(msg.Params[0]) + if err != nil { + client.Send(nil, server.name, ERR_UNKNOWNERROR, "WHO", "Mask isn't valid") + return false + } + mask = casefoldedMask } //TODO(dan): is this used and would I put this param in the Modern doc? @@ -767,7 +782,7 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { for _, channel := range server.channels { whoChannel(client, channel, friends) } - } else if mask.IsChannel() { + } else if mask[0] == '#' { // TODO implement wildcard matching //TODO(dan): ^ only for opers channel := server.channels.Get(mask) @@ -780,31 +795,35 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } } - client.Send(nil, server.nameString, RPL_ENDOFWHO, client.nickString, mask.String(), "End of WHO list") + client.Send(nil, server.name, RPL_ENDOFWHO, client.nick, mask, "End of WHO list") return false } // OPER func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - name := NewName(msg.Params[0]) + name, err := CasefoldName(msg.Params[0]) + if err != nil { + client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect") + return true + } hash := server.operators[name] password := []byte(msg.Params[1]) - err := ComparePassword(hash, password) + err = ComparePassword(hash, password) if (hash == nil) || (err != nil) { - client.Send(nil, server.nameString, ERR_PASSWDMISMATCH, client.nickString, "Password incorrect") + client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect") return true } client.flags[Operator] = true - client.Send(nil, server.nameString, RPL_YOUREOPER, client.nickString, "You are now an IRC operator") + client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator") //TODO(dan): Should this be sent automagically as part of setting the flag/mode? modech := ModeChanges{&ModeChange{ mode: Operator, op: Add, }} - client.Send(nil, server.nameString, "MODE", client.nickString, modech.String()) + client.Send(nil, server.name, "MODE", client.nick, modech.String()) return false } @@ -830,17 +849,17 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { var op ModeOp if client.flags[Away] { op = Add - client.Send(nil, server.nameString, RPL_NOWAWAY, client.nickString, "You have been marked as being away") + client.Send(nil, server.name, RPL_NOWAWAY, client.nick, "You have been marked as being away") } else { op = Remove - client.Send(nil, server.nameString, RPL_UNAWAY, client.nickString, "You are no longer marked as being away") + client.Send(nil, server.name, RPL_UNAWAY, client.nick, "You are no longer marked as being away") } //TODO(dan): Should this be sent automagically as part of setting the flag/mode? modech := ModeChanges{&ModeChange{ mode: Away, op: op, }} - client.Send(nil, server.nameString, "MODE", client.nickString, client.nickString, modech.String()) + client.Send(nil, server.name, "MODE", client.nick, client.nick, modech.String()) // dispatch away-notify for friend := range client.Friends() { @@ -858,14 +877,20 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func isonHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { var nicks = msg.Params + var err error + var casefoldedNick string ison := make([]string, 0) for _, nick := range nicks { - if iclient := server.clients.Get(Name(nick)); iclient != nil { - ison = append(ison, iclient.Nick().String()) + casefoldedNick, err = CasefoldName(nick) + if err != nil { + continue + } + if iclient := server.clients.Get(casefoldedNick); iclient != nil { + ison = append(ison, iclient.nick) } } - client.Send(nil, server.nameString, RPL_ISON, client.nickString, strings.Join(nicks, " ")) + client.Send(nil, server.name, RPL_ISON, client.nick, strings.Join(nicks, " ")) return false } @@ -886,10 +911,9 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { targets := strings.Split(msg.Params[0], ",") message := msg.Params[1] - var target Name for _, targetString := range targets { - target = Name(targetString) - if target.IsChannel() { + target, cerr := CasefoldChannel(targetString) + if cerr == nil { channel := server.channels.Get(target) if channel == nil { // errors silently ignored with NOTICE as per RFC @@ -897,12 +921,17 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } channel.PrivMsg(client, message) } else { + target, err := CasefoldName(targetString) + if err != nil { + continue + } + user := server.clients.Get(target) if user == nil { // errors silently ignored with NOTICE as per RFC continue } - user.Send(nil, client.nickMaskString, "NOTICE", user.nickString, message) + user.Send(nil, client.nickMaskString, "NOTICE", user.nick, message) } } return false @@ -913,7 +942,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { channels := strings.Split(msg.Params[0], ",") users := strings.Split(msg.Params[1], ",") if (len(channels) != len(users)) && (len(users) != 1) { - client.Send(nil, server.nameString, ERR_NEEDMOREPARAMS, client.nickString, "KICK", "Not enough parameters") + client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, "KICK", "Not enough parameters") return false } @@ -931,15 +960,17 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { comment = msg.Params[2] } for chname, nickname := range kicks { - channel := server.channels.Get(Name(chname)) - if channel == nil { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel") + casefoldedChname, err := CasefoldChannel(chname) + channel := server.channels.Get(casefoldedChname) + if err != nil || channel == nil { + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel") continue } - target := server.clients.Get(Name(nickname)) - if target == nil { - client.Send(nil, server.nameString, ERR_NOSUCHNICK, nickname, "No such nick") + casefoldedNickname, err := CasefoldName(nickname) + target := server.clients.Get(casefoldedNickname) + if err != nil || target == nil { + client.Send(nil, server.name, ERR_NOSUCHNICK, nickname, "No such nick") continue } @@ -968,7 +999,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } channel.Kick(client, target, comment) } else { - client.Send(nil, client.server.nameString, ERR_CHANOPRIVSNEEDED, chname, "You're not a channel operator") + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, chname, "You're not a channel operator") } } return false @@ -988,7 +1019,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { //TODO(dan): target server when we have multiple servers //TODO(dan): we should continue just fine if it's this current server though if target != "" { - client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server") + client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server") return false } @@ -1001,15 +1032,16 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } } else { for _, chname := range channels { - channel := server.channels.Get(Name(chname)) - if channel == nil || (!client.flags[Operator] && channel.flags[Secret]) { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel") + casefoldedChname, err := CasefoldChannel(chname) + channel := server.channels.Get(casefoldedChname) + if err != nil || channel == nil || (!client.flags[Operator] && channel.flags[Secret]) { + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel") continue } client.RplList(channel) } } - client.Send(nil, server.nameString, RPL_LISTEND, client.nickString, "End of LIST") + client.Send(nil, server.name, RPL_LISTEND, client.nick, "End of LIST") return false } @@ -1026,7 +1058,7 @@ func (target *Client) RplList(channel *Channel) { } } - target.Send(nil, target.server.nameString, RPL_LIST, target.nickString, channel.nameString, string(memberCount), channel.topic) + target.Send(nil, target.server.name, RPL_LIST, target.nick, channel.name, string(memberCount), channel.topic) } // NAMES [{,}] @@ -1048,9 +1080,10 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } for _, chname := range channels { - channel := server.channels.Get(Name(chname)) - if channel == nil { - client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel") + casefoldedChname, err := CasefoldChannel(chname) + channel := server.channels.Get(casefoldedChname) + if err != nil || channel == nil { + client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel") continue } channel.Names(client) @@ -1064,12 +1097,13 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool if len(msg.Params) > 0 { target = msg.Params[0] } - if (target != "") && (Name(target) != server.name) { - client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server") + casefoldedTarget, err := Casefold(target) + if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) { + client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server") return false } - client.Send(nil, server.nameString, RPL_VERSION, client.nickString, VER, server.nameString) + client.Send(nil, server.name, RPL_VERSION, client.nick, VER, server.name) client.RplISupport() return false } @@ -1079,16 +1113,18 @@ func inviteHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { nickname := msg.Params[0] channelName := msg.Params[1] - target := server.clients.Get(Name(nickname)) - if target == nil { - client.Send(nil, server.nameString, ERR_NOSUCHNICK, client.nickString, nickname, "No such nick") + casefoldedNickname, err := CasefoldName(nickname) + target := server.clients.Get(casefoldedNickname) + if err != nil || target == nil { + client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, nickname, "No such nick") return false } - channel := server.channels.Get(Name(channelName)) - if channel == nil { - client.Send(nil, server.nameString, RPL_INVITING, client.nickString, target.nickString, channelName) - target.Send(nil, client.nickMaskString, "INVITE", target.nickString, channel.nameString) + casefoldedChannelName, err := CasefoldChannel(channelName) + channel := server.channels.Get(casefoldedChannelName) + if err != nil || channel == nil { + client.Send(nil, server.name, RPL_INVITING, client.nick, target.nick, channelName) + target.Send(nil, client.nickMaskString, "INVITE", target.nick, channel.name) return true } @@ -1102,11 +1138,12 @@ func timeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if len(msg.Params) > 0 { target = msg.Params[0] } - if (target != "") && (Name(target) != server.name) { - client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server") + casefoldedTarget, err := Casefold(target) + if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) { + client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server") return false } - client.Send(nil, server.nameString, RPL_TIME, client.nickString, server.nameString, time.Now().Format(time.RFC1123)) + client.Send(nil, server.name, RPL_TIME, client.nick, server.name, time.Now().Format(time.RFC1123)) return false } @@ -1118,13 +1155,14 @@ func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { comment = msg.Params[1] } - target := server.clients.Get(Name(nickname)) - if target == nil { - client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, nickname, "No such nick") + casefoldedNickname, err := CasefoldName(nickname) + target := server.clients.Get(casefoldedNickname) + if err != nil || target == nil { + client.Send(nil, client.server.name, ERR_NOSUCHNICK, nickname, "No such nick") return false } - quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nickString, comment) + quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nick, comment) target.Quit(quitMsg) target.destroy() return false @@ -1143,15 +1181,15 @@ func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // target = msg.Params[2] //} for _, nickname := range nicknames { - results := server.whoWas.Find(Name(nickname), count) + results := server.whoWas.Find(nickname, count) if len(results) == 0 { - client.Send(nil, server.nameString, ERR_WASNOSUCHNICK, client.nickString, nickname, "There was no such nickname") + client.Send(nil, server.name, ERR_WASNOSUCHNICK, client.nick, nickname, "There was no such nickname") } else { for _, whoWas := range results { - client.Send(nil, server.nameString, RPL_WHOWASUSER, client.nickString, whoWas.nickname.String(), whoWas.username.String(), whoWas.hostname.String(), "*", whoWas.realname) + client.Send(nil, server.name, RPL_WHOWASUSER, client.nick, whoWas.nickname, whoWas.username, whoWas.hostname, "*", whoWas.realname) } } - client.Send(nil, server.nameString, RPL_ENDOFWHOWAS, client.nickString, nickname, "End of WHOWAS") + client.Send(nil, server.name, RPL_ENDOFWHOWAS, client.nick, nickname, "End of WHOWAS") } return false } diff --git a/irc/strings.go b/irc/strings.go index 816e4ce9..94e24162 100644 --- a/irc/strings.go +++ b/irc/strings.go @@ -6,91 +6,71 @@ package irc import ( - "regexp" + "errors" "strings" - "golang.org/x/text/unicode/norm" + "golang.org/x/text/secure/precis" ) var ( - // regexps - // these get replaced with real regexes at server load time - - ChannelNameExpr = regexp.MustCompile("^$") - NicknameExpr = regexp.MustCompile("^$") + errInvalidCharacter = errors.New("Invalid character") ) -// Names are normalized and canonicalized to remove formatting marks -// and simplify usage. They are things like hostnames and usermasks. -type Name string - -func NewName(str string) Name { - return Name(norm.NFKC.String(str)) +// Casefold returns a casefolded string, without doing any name or channel character checks. +func Casefold(str string) (string, error) { + return precis.Nickname.String(str) } -func NewNames(strs []string) []Name { - names := make([]Name, len(strs)) - for index, str := range strs { - names[index] = NewName(str) +// CasefoldChannel returns a casefolded version of a channel name. +func CasefoldChannel(name string) (string, error) { + lowered, err := precis.Nickname.String(name) + + if err != nil { + return "", err } - return names + + if lowered[0] != '#' { + return "", errInvalidCharacter + } + + // space can't be used + // , is used as a separator + // * is used in mask matching + // ? is used in mask matching + if strings.Contains(lowered, " ") || strings.Contains(lowered, ",") || + strings.Contains(lowered, "*") || strings.Contains(lowered, "?") { + return "", errInvalidCharacter + } + + return lowered, err } -// tests +// CasefoldName returns a casefolded version of a nick/user name. +func CasefoldName(name string) (string, error) { + lowered, err := precis.Nickname.String(name) -func (name Name) IsChannel() bool { - return ChannelNameExpr.MatchString(name.String()) -} + if err != nil { + return "", err + } -func (name Name) IsNickname() bool { - namestr := name.String() - // * is used for unregistered clients - // * is used for mask matching - // ? is used for mask matching - // . is used to denote server names - // , is used as a separator by the protocol - // ! separates username from nickname - // @ separates nick+user from hostname + // space can't be used + // , is used as a separator + // * is used in mask matching + // ? is used in mask matching + // . denotes a server name + // ! separates nickname from username + // @ separates username from hostname + // : means trailing // # is a channel prefix // ~&@%+ are channel membership prefixes - // - is typically disallowed from first char of nicknames - // nicknames can't start with digits - if strings.Contains(namestr, "*") || strings.Contains(namestr, "?") || - strings.Contains(namestr, ".") || strings.Contains(namestr, ",") || - strings.Contains(namestr, "!") || strings.Contains(namestr, "@") || - strings.Contains("#~&@%+-1234567890", string(namestr[0])) { - return false + // - I feel like disallowing + if strings.Contains(lowered, " ") || strings.Contains(lowered, ",") || + strings.Contains(lowered, "*") || strings.Contains(lowered, "?") || + strings.Contains(lowered, ".") || strings.Contains(lowered, "!") || + strings.Contains(lowered, "@") || + strings.Contains("#~&@%+-", string(lowered[0])) { + return "", errInvalidCharacter } - // names that look like hostnames are restricted to servers, as with other ircds - if IsHostname(namestr) { - return false - } - return NicknameExpr.MatchString(namestr) -} - -// conversions - -func (name Name) String() string { - return string(name) -} - -func (name Name) ToLower() Name { - return Name(strings.ToLower(name.String())) -} - -// It's safe to coerce a Name to Text. Name is a strict subset of Text. -func (name Name) Text() Text { - return Text(name) -} - -// Text is PRIVMSG, NOTICE, or TOPIC data. It's canonicalized UTF8 -// data to simplify but keeps all formatting. -type Text string - -func NewText(str string) Text { - return Text(norm.NFC.String(str)) -} - -func (text Text) String() string { - return string(text) + + return lowered, err } diff --git a/irc/types.go b/irc/types.go index 7a15b74c..bf119aa9 100644 --- a/irc/types.go +++ b/irc/types.go @@ -13,25 +13,29 @@ import ( // simple types // -type ChannelNameMap map[Name]*Channel +type ChannelNameMap map[string]*Channel -func (channels ChannelNameMap) Get(name Name) *Channel { - return channels[name.ToLower()] +func (channels ChannelNameMap) Get(name string) *Channel { + name, err := CasefoldChannel(name) + if err == nil { + return channels[name] + } + return nil } func (channels ChannelNameMap) Add(channel *Channel) error { - if channels[channel.name.ToLower()] != nil { + if channels[channel.nameCasefolded] != nil { return fmt.Errorf("%s: already set", channel.name) } - channels[channel.name.ToLower()] = channel + channels[channel.nameCasefolded] = channel return nil } func (channels ChannelNameMap) Remove(channel *Channel) error { - if channel != channels[channel.name.ToLower()] { + if channel != channels[channel.nameCasefolded] { return fmt.Errorf("%s: mismatch", channel.name) } - delete(channels, channel.name.ToLower()) + delete(channels, channel.nameCasefolded) return nil } @@ -118,6 +122,6 @@ func (channels ChannelSet) First() *Channel { // type Identifiable interface { - Id() Name - Nick() Name + Id() string + Nick() string } diff --git a/irc/whowas.go b/irc/whowas.go index 08077877..16d94a43 100644 --- a/irc/whowas.go +++ b/irc/whowas.go @@ -1,4 +1,5 @@ // Copyright (c) 2012-2014 Jeremy Latt +// Copyright (c) 2016- Daniel Oaks // released under the MIT license package irc @@ -10,10 +11,11 @@ type WhoWasList struct { } type WhoWas struct { - nickname Name - username Name - hostname Name - realname string + nicknameCasefolded string + nickname string + username string + hostname string + realname string } func NewWhoWasList(size uint) *WhoWasList { @@ -24,10 +26,11 @@ func NewWhoWasList(size uint) *WhoWasList { func (list *WhoWasList) Append(client *Client) { list.buffer[list.end] = &WhoWas{ - nickname: client.Nick(), - username: client.username, - hostname: client.hostname, - realname: client.realname, + nicknameCasefolded: client.nickCasefolded, + nickname: client.nick, + username: client.username, + hostname: client.hostname, + realname: client.realname, } list.end = (list.end + 1) % len(list.buffer) if list.end == list.start { @@ -35,10 +38,16 @@ func (list *WhoWasList) Append(client *Client) { } } -func (list *WhoWasList) Find(nickname Name, limit int64) []*WhoWas { +func (list *WhoWasList) Find(nickname string, limit int64) []*WhoWas { results := make([]*WhoWas, 0) + + casefoldedNickname, err := CasefoldName(nickname) + if err != nil { + return results + } + for whoWas := range list.Each() { - if nickname != whoWas.nickname { + if casefoldedNickname != whoWas.nicknameCasefolded { continue } results = append(results, whoWas)