3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-26 04:32:36 +01:00

Move from ascii(ish) unicode encoding to prelim rfc7700 using functions instead

This commit is contained in:
Daniel Oaks 2016-10-11 23:51:46 +10:00
parent 2bfcc553ce
commit 5e72409695
16 changed files with 587 additions and 515 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}
// <mode> <mode params>
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)
}
}

View File

@ -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 != "*"
}
// <mode>
@ -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)
}

View File

@ -1,10 +1,13 @@
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// 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, "?")

View File

@ -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 {

View File

@ -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
}

View File

@ -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...)...)
}
}

View File

@ -120,7 +120,7 @@ func (changes ChannelModeChanges) String() string {
}
type ChannelModeCommand struct {
channel Name
channel string
changes ChannelModeChanges
}
@ -209,8 +209,9 @@ var (
// MODE <target> [<modestring> [<mode arguments>...]]
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 <target> [<modestring> [<mode arguments>...]]
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 <target> [<modestring> [<mode arguments>...]]
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
}

View File

@ -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-."

View File

@ -4,7 +4,11 @@
package irc
import "github.com/DanielOaks/girc-go/ircmsg"
import (
"strings"
"github.com/DanielOaks/girc-go/ircmsg"
)
// NICK <nickname>
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
}

View File

@ -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
}

View File

@ -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 [<channel modes with a parameter>] 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 <password>
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 <username> * 0 <realname>
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 <server1> [<server2>]
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 <channel> [<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")
}
// <channel> <user> <host> <server> <nick> ( "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 <name> <password>
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 [<channel>{,<channel>}]
@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,4 +1,5 @@
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// 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)