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 // sasl abort
if len(msg.Params) == 1 && msg.Params[0] == "*" { if len(msg.Params) == 1 && msg.Params[0] == "*" {
if client.saslInProgress { 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 { } 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.saslInProgress = false
client.saslMechanism = "" client.saslMechanism = ""
@ -98,9 +98,9 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
if mechanismIsEnabled { if mechanismIsEnabled {
client.saslInProgress = true client.saslInProgress = true
client.saslMechanism = mechanism client.saslMechanism = mechanism
client.Send(nil, server.nameString, "AUTHENTICATE", "+") client.Send(nil, server.name, "AUTHENTICATE", "+")
} else { } 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 return false
@ -110,7 +110,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
rawData := msg.Params[0] rawData := msg.Params[0]
if len(rawData) > 400 { 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.saslInProgress = false
client.saslMechanism = "" client.saslMechanism = ""
client.saslValue = "" client.saslValue = ""
@ -119,7 +119,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
client.saslValue += rawData client.saslValue += rawData
// allow 4 'continuation' lines before rejecting for length // allow 4 'continuation' lines before rejecting for length
if len(client.saslValue) > 400*4 { 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.saslInProgress = false
client.saslMechanism = "" client.saslMechanism = ""
client.saslValue = "" client.saslValue = ""
@ -136,7 +136,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage)
if client.saslValue != "+" { if client.saslValue != "+" {
data, err = base64.StdEncoding.DecodeString(client.saslValue) data, err = base64.StdEncoding.DecodeString(client.saslValue)
if err != nil { 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.saslInProgress = false
client.saslMechanism = "" client.saslMechanism = ""
client.saslValue = "" 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 // like 100% not required, but it's good to be safe I guess
if !handlerExists { 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.saslInProgress = false
client.saslMechanism = "" client.saslMechanism = ""
client.saslValue = "" client.saslValue = ""
@ -169,7 +169,7 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
splitValue := bytes.Split(value, []byte{'\000'}) splitValue := bytes.Split(value, []byte{'\000'})
if len(splitValue) != 3 { 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 return false
} }
@ -177,17 +177,20 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
authzid := string(splitValue[1]) authzid := string(splitValue[1])
if accountKey != authzid { 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 return false
} }
// casefolding, rough for now bit will improve later.
// keep it the same as in the REG CREATE stage // 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. // load and check acct data all in one update to prevent races.
// as noted elsewhere, change to proper locking for Account type later probably // 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) creds, err := loadAccountCredentials(tx, accountKey)
if err != nil { if err != nil {
return err return err
@ -213,19 +216,19 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
}) })
if err != nil { 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 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.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.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful") client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "SASL authentication successful")
return false return false
} }
// authExternalHandler parses the SASL EXTERNAL mechanism. // authExternalHandler parses the SASL EXTERNAL mechanism.
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool { func authExternalHandler(server *Server, client *Client, mechanism string, value []byte) bool {
if client.certfp == "" { 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 return false
} }
@ -261,11 +264,11 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value
}) })
if err != nil { 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 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.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.nameString, RPL_SASLSUCCESS, client.nickString, "SASL authentication successful") client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "SASL authentication successful")
return false 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 // 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 // and let it connect to the server (otherwise it doesn't respond to the CAP
// message with anything and just hangs on connection) // 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": 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": case "REQ":
// make sure all capabilities actually exist // make sure all capabilities actually exist
for capability := range capabilities { for capability := range capabilities {
if !SupportedCapabilities[capability] { 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 return false
} }
} }
for capability := range capabilities { for capability := range capabilities {
client.capabilities[capability] = true 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": case "END":
if !client.registered { if !client.registered {
@ -132,7 +132,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
default: 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 return false
} }

View File

@ -6,29 +6,36 @@
package irc package irc
import ( import (
"fmt"
"log" "log"
"strconv" "strconv"
"time" "time"
) )
type Channel struct { type Channel struct {
flags ChannelModeSet flags ChannelModeSet
lists map[ChannelMode]*UserMaskSet lists map[ChannelMode]*UserMaskSet
key string key string
members MemberSet members MemberSet
name Name name string
nameString string nameCasefolded string
server *Server server *Server
createdTime time.Time createdTime time.Time
topic string topic string
topicSetBy string topicSetBy string
topicSetTime time.Time topicSetTime time.Time
userLimit uint64 userLimit uint64
} }
// NewChannel creates a new channel from a `Server` and a `name` // NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server. // 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{ channel := &Channel{
flags: make(ChannelModeSet), flags: make(ChannelModeSet),
lists: map[ChannelMode]*UserMaskSet{ lists: map[ChannelMode]*UserMaskSet{
@ -36,10 +43,10 @@ func NewChannel(s *Server, name Name, addDefaultModes bool) *Channel {
ExceptMask: NewUserMaskSet(), ExceptMask: NewUserMaskSet(),
InviteMask: NewUserMaskSet(), InviteMask: NewUserMaskSet(),
}, },
members: make(MemberSet), members: make(MemberSet),
name: name, name: name,
nameString: name.String(), nameCasefolded: casefoldedName,
server: s, server: s,
} }
if addDefaultModes { if addDefaultModes {
@ -60,7 +67,7 @@ func (channel *Channel) IsEmpty() bool {
func (channel *Channel) Names(client *Client) { func (channel *Channel) Names(client *Client) {
currentNicks := channel.Nicks(client) currentNicks := channel.Nicks(client)
// assemble and send replies // 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 var buffer string
for _, nick := range currentNicks { for _, nick := range currentNicks {
if buffer == "" { if buffer == "" {
@ -69,7 +76,7 @@ func (channel *Channel) Names(client *Client) {
} }
if len(buffer)+1+len(nick) > maxNamLen { 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 buffer = nick
continue continue
} }
@ -78,8 +85,8 @@ func (channel *Channel) Names(client *Client) {
buffer += nick buffer += nick
} }
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)
client.Send(nil, client.server.nameString, RPL_ENDOFNAMES, client.nickString, channel.nameString, "End of NAMES list") client.Send(nil, client.server.name, RPL_ENDOFNAMES, client.nick, channel.name, "End of NAMES list")
} }
func (channel *Channel) ClientIsOperator(client *Client) bool { func (channel *Channel) ClientIsOperator(client *Client) bool {
@ -117,25 +124,21 @@ func (channel *Channel) Nicks(target *Client) []string {
if isUserhostInNames { if isUserhostInNames {
nicks[i] += client.nickMaskString nicks[i] += client.nickMaskString
} else { } else {
nicks[i] += client.nickString nicks[i] += client.nick
} }
i += 1 i += 1
} }
return nicks return nicks
} }
func (channel *Channel) Id() Name { func (channel *Channel) Id() string {
return channel.name return channel.name
} }
func (channel *Channel) Nick() Name { func (channel *Channel) Nick() string {
return channel.name return channel.name
} }
func (channel *Channel) String() string {
return channel.Id().String()
}
// <mode> <mode params> // <mode> <mode params>
func (channel *Channel) ModeString(client *Client) (str string) { func (channel *Channel) ModeString(client *Client) (str string) {
isMember := client.flags[Operator] || channel.members.Has(client) isMember := client.flags[Operator] || channel.members.Has(client)
@ -185,33 +188,33 @@ func (channel *Channel) Join(client *Client, key string) {
} }
if channel.IsFull() { 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 return
} }
if !channel.CheckKey(key) { 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 return
} }
isInvited := channel.lists[InviteMask].Match(client.UserHost()) isInvited := channel.lists[InviteMask].Match(client.UserHost())
if channel.flags[InviteOnly] && !isInvited { 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 return
} }
if channel.lists[BanMask].Match(client.UserHost()) && if channel.lists[BanMask].Match(client.UserHost()) &&
!isInvited && !isInvited &&
!channel.lists[ExceptMask].Match(client.UserHost()) { !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 return
} }
for member := range channel.members { for member := range channel.members {
if member.capabilities[ExtendedJoin] { 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 { } 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] { 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 { } else {
client.Send(nil, client.nickMaskString, "JOIN", channel.nameString) client.Send(nil, client.nickMaskString, "JOIN", channel.name)
} }
channel.GetTopic(client) channel.GetTopic(client)
channel.Names(client) channel.Names(client)
@ -234,39 +237,39 @@ func (channel *Channel) Join(client *Client, key string) {
func (channel *Channel) Part(client *Client, message string) { func (channel *Channel) Part(client *Client, message string) {
if !channel.members.Has(client) { if !channel.members.Has(client) {
client.Send(nil, client.server.nameString, ERR_NOTONCHANNEL, channel.nameString, "You're not on that channel") client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel")
return return
} }
for member := range channel.members { 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) channel.Quit(client)
} }
func (channel *Channel) GetTopic(client *Client) { func (channel *Channel) GetTopic(client *Client) {
if !channel.members.Has(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 return
} }
if channel.topic == "" { 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 return
} }
client.Send(nil, client.server.nameString, RPL_TOPIC, client.nickString, channel.nameString, channel.topic) client.Send(nil, client.server.name, RPL_TOPIC, client.nick, channel.name, 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_TOPICTIME, client.nick, channel.name, channel.topicSetBy, strconv.FormatInt(channel.topicSetTime.Unix(), 10))
} }
func (channel *Channel) SetTopic(client *Client, topic string) { func (channel *Channel) SetTopic(client *Client, topic string) {
if !(client.flags[Operator] || channel.members.Has(client)) { 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 return
} }
if channel.flags[OpOnlyTopic] && !channel.ClientIsOperator(client) { 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 return
} }
@ -275,11 +278,11 @@ func (channel *Channel) SetTopic(client *Client, topic string) {
} }
channel.topic = topic channel.topic = topic
channel.topicSetBy = client.nickString channel.topicSetBy = client.nick
channel.topicSetTime = time.Now() channel.topicSetTime = time.Now()
for member := range channel.members { 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() channel.Persist()
@ -301,21 +304,21 @@ func (channel *Channel) CanSpeak(client *Client) bool {
func (channel *Channel) PrivMsg(client *Client, message string) { func (channel *Channel) PrivMsg(client *Client, message string) {
if !channel.CanSpeak(client) { 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 return
} }
for member := range channel.members { for member := range channel.members {
if member == client { if member == client {
continue 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, func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode,
op ModeOp) bool { op ModeOp) bool {
if !channel.ClientIsOperator(client) { 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 return false
} }
@ -341,20 +344,19 @@ func (channel *Channel) applyModeMember(client *Client, mode ChannelMode,
op ModeOp, nick string) *ChannelModeChange { op ModeOp, nick string) *ChannelModeChange {
if nick == "" { if nick == "" {
//TODO(dan): shouldn't this be handled before it reaches this function? //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 return nil
} }
target := channel.server.clients.Get(Name(nick)) casefoldedName, err := CasefoldName(nick)
if target == nil { target := channel.server.clients.Get(casefoldedName)
//TODO(dan): investigate using NOSUCHNICK and NOSUCHCHANNEL specifically as that other IRCd (insp?) does, if err != nil || target == nil {
// since I think that would make sense client.Send(nil, client.server.name, ERR_NOSUCHNICK, nick, "No such nick")
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, nick, "No such nick")
return nil return nil
} }
if !channel.members.Has(target) { 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 return nil
} }
@ -394,8 +396,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) {
client.RplEndOfMaskList(mode, channel)*/ client.RplEndOfMaskList(mode, channel)*/
} }
func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, mask string) bool {
mask Name) bool {
list := channel.lists[mode] list := channel.lists[mode]
if list == nil { if list == nil {
// This should never happen, but better safe than panicky. // 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) { 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 return false
} }
@ -453,14 +454,14 @@ func (channel *Channel) Persist() (err error) {
func (channel *Channel) Notice(client *Client, message string) { func (channel *Channel) Notice(client *Client, message string) {
if !channel.CanSpeak(client) { 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 return
} }
for member := range channel.members { for member := range channel.members {
if member == client { if member == client {
continue 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) { func (channel *Channel) Kick(client *Client, target *Client, comment string) {
if !(client.flags[Operator] || channel.members.Has(client)) { 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 return
} }
if !channel.ClientIsOperator(client) { 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 return
} }
if !channel.members.Has(target) { 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 return
} }
@ -492,19 +493,19 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string) {
} }
for member := range channel.members { 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) channel.Quit(target)
} }
func (channel *Channel) Invite(invitee *Client, inviter *Client) { func (channel *Channel) Invite(invitee *Client, inviter *Client) {
if channel.flags[InviteOnly] && !channel.ClientIsOperator(inviter) { 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 return
} }
if !channel.members.Has(inviter) { 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 return
} }
@ -513,10 +514,10 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
channel.Persist() channel.Persist()
} }
//TODO(dan): should inviter.server.nameString here be inviter.nickMaskString ? //TODO(dan): should inviter.server.name here be inviter.nickMaskString ?
inviter.Send(nil, inviter.server.nameString, RPL_INVITING, invitee.nickString, channel.nameString) inviter.Send(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name)
invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nickString, channel.nameString) invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name)
if invitee.flags[Away] { 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 { type Client struct {
account *ClientAccount account *ClientAccount
atime time.Time atime time.Time
authorized bool authorized bool
awayMessage string awayMessage string
capabilities CapabilitySet capabilities CapabilitySet
capState CapState capState CapState
certfp string certfp string
channels ChannelSet channels ChannelSet
ctime time.Time ctime time.Time
flags map[UserMode]bool flags map[UserMode]bool
isDestroyed bool isDestroyed bool
isQuitting bool isQuitting bool
hasQuit bool hasQuit bool
hops uint hops uint
hostname Name hostname string
idleTimer *time.Timer idleTimer *time.Timer
nick Name nick string
nickString string // cache for nick string since it's used with most numerics nickCasefolded string
nickMaskString string // cache for nickmask string since it's used with lots of replies nickMaskString string // cache for nickmask string since it's used with lots of replies
quitTimer *time.Timer nickMaskCasefolded string
realname string quitTimer *time.Timer
registered bool realname string
saslInProgress bool registered bool
saslMechanism string saslInProgress bool
saslValue string saslMechanism string
server *Server saslValue string
socket *Socket server *Server
username Name socket *Socket
username string
} }
func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { 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, server: server,
socket: &socket, socket: &socket,
account: &NoAccount, 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 nickMaskString: "*", // * is used until actual nick is given
} }
if isTLS { if isTLS {
@ -96,10 +98,10 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds) resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
if err == nil { if err == nil {
username := resp.Identifier username := resp.Identifier
//TODO(dan): replace this with IsUsername/IsIRCName? _, err := CasefoldName(username) // ensure it's a valid username
if Name(username).IsNickname() { if err == nil {
client.Notice("*** Found your username") 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 // we don't need to updateNickMask here since nickMask is not used for anything yet
} else { } else {
client.Notice("*** Got a malformed username, ignoring") client.Notice("*** Got a malformed username, ignoring")
@ -144,7 +146,7 @@ func (client *Client) run() {
cmd, exists := Commands[msg.Command] cmd, exists := Commands[msg.Command]
if !exists { 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 continue
} }
@ -196,7 +198,7 @@ func (client *Client) Touch() {
} }
func (client *Client) Idle() { func (client *Client) Idle() {
client.Send(nil, "", "PING", client.nickString) client.Send(nil, "", "PING", client.nick)
if client.quitTimer == nil { if client.quitTimer == nil {
client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout) client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout)
@ -226,11 +228,11 @@ func (client *Client) IdleSeconds() uint64 {
} }
func (client *Client) HasNick() bool { func (client *Client) HasNick() bool {
return client.nick != "" return client.nick != "" && client.nick != "*"
} }
func (client *Client) HasUsername() bool { func (client *Client) HasUsername() bool {
return client.username != "" return client.username != "" && client.username != "*"
} }
// <mode> // <mode>
@ -244,29 +246,14 @@ func (c *Client) ModeString() (str string) {
return return
} }
func (c *Client) UserHost() Name { func (c *Client) UserHost() string {
username := "*" return fmt.Sprintf("%s!%s@%s", c.nick, c.username, c.hostname)
if c.HasUsername() {
username = c.username.String()
}
return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname))
} }
func (c *Client) Nick() Name { func (c *Client) Id() string {
if c.HasNick() {
return c.nick
}
return Name("*")
}
func (c *Client) Id() Name {
return c.UserHost() return c.UserHost()
} }
func (c *Client) String() string {
return c.Id().String()
}
// Friends refers to clients that share a channel with this client. // Friends refers to clients that share a channel with this client.
func (client *Client) Friends() ClientSet { func (client *Client) Friends() ClientSet {
friends := make(ClientSet) friends := make(ClientSet)
@ -280,30 +267,42 @@ func (client *Client) Friends() ClientSet {
} }
func (client *Client) updateNickMask() { func (client *Client) updateNickMask() {
client.nickString = client.nick.String() casefoldedName, err := CasefoldName(client.nick)
client.nickMaskString = fmt.Sprintf("%s!%s@%s", client.nickString, client.username, client.hostname) 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() { if client.HasNick() {
Log.error.Printf("%s nickname already set!", client) Log.error.Printf("%s nickname already set!", client.nickMaskString)
return return
} }
fmt.Println("Setting nick to:", nickname, "from", client.nick)
client.nick = nickname client.nick = nickname
client.updateNickMask() client.updateNickMask()
client.server.clients.Add(client) client.server.clients.Add(client)
} }
func (client *Client) ChangeNickname(nickname Name) { func (client *Client) ChangeNickname(nickname string) {
origNickMask := client.nickMaskString origNickMask := client.nickMaskString
client.server.clients.Remove(client) client.server.clients.Remove(client)
client.server.whoWas.Append(client) client.server.whoWas.Append(client)
client.nick = nickname client.nick = nickname
client.updateNickMask() client.updateNickMask()
client.server.clients.Add(client) client.server.clients.Add(client)
client.Send(nil, origNickMask, "NICK", nickname.String()) client.Send(nil, origNickMask, "NICK", nickname)
for friend := range client.Friends() { 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() line, err := message.Line()
if err != nil { if err != nil {
// try not to fail quietly - especially useful when running tests, as a note to dig deeper // 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() line, _ := message.Line()
client.socket.Write(line) client.socket.Write(line)
return err 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. // Notice sends the client a notice from the server.
func (client *Client) Notice(text string) { 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) 2012-2014 Jeremy Latt
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license // released under the MIT license
package irc package irc
import ( import (
"errors" "errors"
"fmt"
"log"
"regexp" "regexp"
"strings" "strings"
@ -17,31 +20,35 @@ var (
ErrNicknameMismatch = errors.New("nickname mismatch") ErrNicknameMismatch = errors.New("nickname mismatch")
) )
func ExpandUserHost(userhost Name) (expanded Name) { func ExpandUserHost(userhost string) (expanded string) {
expanded = userhost expanded = userhost
// fill in missing wildcards for nicks // fill in missing wildcards for nicks
//TODO(dan): this would fail with dan@lol, do we want to accommodate that? //TODO(dan): this would fail with dan@lol, do we want to accommodate that?
if !strings.Contains(expanded.String(), "!") { if !strings.Contains(expanded, "!") {
expanded += "!*" expanded += "!*"
} }
if !strings.Contains(expanded.String(), "@") { if !strings.Contains(expanded, "@") {
expanded += "@*" expanded += "@*"
} }
return return
} }
type ClientLookupSet struct { type ClientLookupSet struct {
byNick map[Name]*Client byNick map[string]*Client
} }
func NewClientLookupSet() *ClientLookupSet { func NewClientLookupSet() *ClientLookupSet {
return &ClientLookupSet{ return &ClientLookupSet{
byNick: make(map[Name]*Client), byNick: make(map[string]*Client),
} }
} }
func (clients *ClientLookupSet) Get(nick Name) *Client { func (clients *ClientLookupSet) Get(nick string) *Client {
return clients.byNick[nick.ToLower()] casefoldedName, err := CasefoldName(nick)
if err == nil {
return clients.byNick[casefoldedName]
}
return nil
} }
func (clients *ClientLookupSet) Add(client *Client) error { func (clients *ClientLookupSet) Add(client *Client) error {
@ -51,7 +58,7 @@ func (clients *ClientLookupSet) Add(client *Client) error {
if clients.Get(client.nick) != nil { if clients.Get(client.nick) != nil {
return ErrNicknameInUse return ErrNicknameInUse
} }
clients.byNick[client.Nick().ToLower()] = client clients.byNick[client.nickCasefolded] = client
return nil return nil
} }
@ -62,20 +69,21 @@ func (clients *ClientLookupSet) Remove(client *Client) error {
if clients.Get(client.nick) != client { if clients.Get(client.nick) != client {
return ErrNicknameMismatch return ErrNicknameMismatch
} }
delete(clients.byNick, client.nick.ToLower()) delete(clients.byNick, client.nickCasefolded)
return nil return nil
} }
func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) { func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
set = make(ClientSet) set = make(ClientSet)
userhost = ExpandUserHost(userhost) userhost, err := Casefold(ExpandUserHost(userhost))
matcher := ircmatch.MakeMatch(userhost.String()) if err != nil {
return set
}
matcher := ircmatch.MakeMatch(userhost)
var casemappedNickMask string
for _, client := range clients.byNick { for _, client := range clients.byNick {
casemappedNickMask = NewName(client.nickMaskString).String() if matcher.Match(client.nickMaskCasefolded) {
if matcher.Match(casemappedNickMask) {
set.Add(client) set.Add(client)
} }
} }
@ -83,14 +91,15 @@ func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
return set return set
} }
func (clients *ClientLookupSet) Find(userhost Name) *Client { func (clients *ClientLookupSet) Find(userhost string) *Client {
userhost = ExpandUserHost(userhost) userhost, err := Casefold(ExpandUserHost(userhost))
matcher := ircmatch.MakeMatch(userhost.String()) if err != nil {
return nil
}
matcher := ircmatch.MakeMatch(userhost)
var casemappedNickMask string
for _, client := range clients.byNick { for _, client := range clients.byNick {
casemappedNickMask = NewName(client.nickMaskString).String() if matcher.Match(client.nickMaskCasefolded) {
if matcher.Match(casemappedNickMask) {
return client return client
} }
} }
@ -105,26 +114,31 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
//TODO(dan): move this over to generally using glob syntax instead? //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?) // kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
type UserMaskSet struct { type UserMaskSet struct {
masks map[Name]bool masks map[string]bool
regexp *regexp.Regexp regexp *regexp.Regexp
} }
func NewUserMaskSet() *UserMaskSet { func NewUserMaskSet() *UserMaskSet {
return &UserMaskSet{ return &UserMaskSet{
masks: make(map[Name]bool), masks: make(map[string]bool),
} }
} }
func (set *UserMaskSet) Add(mask Name) bool { func (set *UserMaskSet) Add(mask string) bool {
if set.masks[mask] { casefoldedMask, err := Casefold(mask)
if err != nil {
log.Println(fmt.Sprintf("ERROR: Could not add mask to usermaskset: [%s]", mask))
return false return false
} }
set.masks[mask] = true if set.masks[casefoldedMask] {
return false
}
set.masks[casefoldedMask] = true
set.setRegexp() set.setRegexp()
return true return true
} }
func (set *UserMaskSet) AddAll(masks []Name) (added bool) { func (set *UserMaskSet) AddAll(masks []string) (added bool) {
for _, mask := range masks { for _, mask := range masks {
if !added && !set.masks[mask] { if !added && !set.masks[mask] {
added = true added = true
@ -135,7 +149,7 @@ func (set *UserMaskSet) AddAll(masks []Name) (added bool) {
return return
} }
func (set *UserMaskSet) Remove(mask Name) bool { func (set *UserMaskSet) Remove(mask string) bool {
if !set.masks[mask] { if !set.masks[mask] {
return false return false
} }
@ -144,18 +158,18 @@ func (set *UserMaskSet) Remove(mask Name) bool {
return true return true
} }
func (set *UserMaskSet) Match(userhost Name) bool { func (set *UserMaskSet) Match(userhost string) bool {
if set.regexp == nil { if set.regexp == nil {
return false return false
} }
return set.regexp.MatchString(userhost.String()) return set.regexp.MatchString(userhost)
} }
func (set *UserMaskSet) String() string { func (set *UserMaskSet) String() string {
masks := make([]string, len(set.masks)) masks := make([]string, len(set.masks))
index := 0 index := 0
for mask := range set.masks { for mask := range set.masks {
masks[index] = mask.String() masks[index] = mask
index += 1 index += 1
} }
return strings.Join(masks, " ") return strings.Join(masks, " ")
@ -176,7 +190,7 @@ func (set *UserMaskSet) setRegexp() {
maskExprs := make([]string, len(set.masks)) maskExprs := make([]string, len(set.masks))
index := 0 index := 0
for mask := range set.masks { for mask := range set.masks {
manyParts := strings.Split(mask.String(), "*") manyParts := strings.Split(mask, "*")
manyExprs := make([]string, len(manyParts)) manyExprs := make([]string, len(manyParts))
for mindex, manyPart := range manyParts { for mindex, manyPart := range manyParts {
oneParts := strings.Split(manyPart, "?") oneParts := strings.Split(manyPart, "?")

View File

@ -24,11 +24,11 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
return false return false
} }
if cmd.oper && !client.flags[Operator] { 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 return false
} }
if len(msg.Params) < cmd.minParams { 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 return false
} }
if !cmd.leaveClientActive { if !cmd.leaveClientActive {

View File

@ -103,22 +103,32 @@ type Config struct {
} }
} }
func (conf *Config) Operators() map[Name][]byte { func (conf *Config) Operators() map[string][]byte {
operators := make(map[Name][]byte) operators := make(map[string][]byte)
for name, opConf := range conf.Operator { 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 return operators
} }
func (conf *Config) TLSListeners() map[Name]*tls.Config { func (conf *Config) TLSListeners() map[string]*tls.Config {
tlsListeners := make(map[Name]*tls.Config) tlsListeners := make(map[string]*tls.Config)
for s, tlsListenersConf := range conf.Server.TLSListeners { for s, tlsListenersConf := range conf.Server.TLSListeners {
config, err := tlsListenersConf.Config() config, err := tlsListenersConf.Config()
if err != nil { if err != nil {
log.Fatal(err) 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 return tlsListeners
} }

View File

@ -70,6 +70,6 @@ func (il *ISupportList) RegenerateCachedReply() {
func (client *Client) RplISupport() { func (client *Client) RplISupport() {
for _, tokenline := range client.server.isupport.CachedReply { for _, tokenline := range client.server.isupport.CachedReply {
// ugly trickery ahead // 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 { type ChannelModeCommand struct {
channel Name channel string
changes ChannelModeChanges changes ChannelModeChanges
} }
@ -209,8 +209,9 @@ var (
// MODE <target> [<modestring> [<mode arguments>...]] // MODE <target> [<modestring> [<mode arguments>...]]
func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
name := NewName(msg.Params[0]) _, errChan := CasefoldChannel(msg.Params[0])
if name.IsChannel() {
if errChan != nil {
return cmodeHandler(server, client, msg) return cmodeHandler(server, client, msg)
} else { } else {
return umodeHandler(server, client, msg) return umodeHandler(server, client, msg)
@ -219,12 +220,12 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// MODE <target> [<modestring> [<mode arguments>...]] // MODE <target> [<modestring> [<mode arguments>...]]
func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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) target := server.clients.Get(nickname)
if target == nil { if err != nil || target == nil {
client.Send(nil, server.nameString, ERR_NOSUCHNICK, client.nickString, msg.Params[0], "No such nick") client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], "No such nick")
return false 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 // point SAMODE at this handler too, if they are operator and SAMODE was called then fine
if client != target && !client.flags[Operator] { if client != target && !client.flags[Operator] {
if len(msg.Params) > 1 { 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 { } 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 return false
} }
@ -249,7 +250,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if (op == Add) || (op == Remove) { if (op == Add) || (op == Remove) {
modeArg = modeArg[1:] modeArg = modeArg[1:]
} else { } 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 return false
} }
@ -298,20 +299,20 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
if len(applied) > 0 { 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 { } 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 return false
} }
// MODE <target> [<modestring> [<mode arguments>...]] // MODE <target> [<modestring> [<mode arguments>...]]
func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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) channel := server.channels.Get(channelName)
if channel == nil { if err != nil || channel == nil {
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, msg.Params[0], "No such channel") client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], "No such channel")
return false return false
} }
@ -327,7 +328,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if (op == Add) || (op == Remove) { if (op == Add) || (op == Remove) {
modeArg = modeArg[1:] modeArg = modeArg[1:]
} else { } 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 return false
} }
@ -380,7 +381,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
list := channel.lists[change.mode] list := channel.lists[change.mode]
if list == nil { if list == nil {
// This should never happen, but better safe than panicky. // 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 return false
} }
@ -389,13 +390,19 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
continue continue
} }
// confirm mask looks valid
mask, err = Casefold(mask)
if err != nil {
continue
}
switch change.op { switch change.op {
case Add: case Add:
list.Add(Name(mask)) list.Add(mask)
applied = append(applied, change) applied = append(applied, change)
case Remove: case Remove:
list.Remove(Name(mask)) list.Remove(mask)
applied = append(applied, change) 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 !hasPrivs {
if change.op == Remove && name.ToLower() == client.nick.ToLower() { if change.op == Remove && casefoldedName == client.nickCasefolded {
// success! // success!
} else { } 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 continue
} }
} }
@ -481,13 +491,13 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if len(applied) > 0 { if len(applied) > 0 {
//TODO(dan): we should change the name of String and make it return a slice here //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...) client.Send(nil, client.nickMaskString, "MODE", args...)
} else { } else {
//TODO(dan): we should just make ModeString return a slice here //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_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 return false
} }

View File

@ -9,27 +9,27 @@ import (
"strings" "strings"
) )
func IPString(addr net.Addr) Name { func IPString(addr net.Addr) string {
addrStr := addr.String() addrStr := addr.String()
ipaddr, _, err := net.SplitHostPort(addrStr) ipaddr, _, err := net.SplitHostPort(addrStr)
if err != nil { 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)) return LookupHostname(IPString(addr))
} }
func LookupHostname(addr Name) Name { func LookupHostname(addr string) string {
names, err := net.LookupAddr(addr.String()) names, err := net.LookupAddr(addr)
if err != nil { if err != nil || !IsHostname(names[0]) {
return Name(addr) // return original address
return addr
} }
hostname := strings.TrimSuffix(names[0], ".") return names[0]
return Name(hostname)
} }
var allowedHostnameChars = "abcdefghijklmnopqrstuvwxyz1234567890-." var allowedHostnameChars = "abcdefghijklmnopqrstuvwxyz1234567890-."

View File

@ -4,7 +4,11 @@
package irc package irc
import "github.com/DanielOaks/girc-go/ircmsg" import (
"strings"
"github.com/DanielOaks/girc-go/ircmsg"
)
// NICK <nickname> // NICK <nickname>
func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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 return true
} }
nickname := NewName(msg.Params[0]) nickname, err := CasefoldName(msg.Params[0])
if len(nickname) < 1 { if len(strings.TrimSpace(msg.Params[0])) < 1 {
client.Send(nil, server.nameString, ERR_NONICKNAMEGIVEN, client.nickString, "No nickname given") client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
return false return false
} }
if !nickname.IsNickname() { if err != nil || len(strings.TrimSpace(msg.Params[0])) > server.limits.NickLen {
client.Send(nil, server.nameString, ERR_ERRONEUSNICKNAME, client.nickString, msg.Params[0], "Erroneous nickname") client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
return false 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 //TODO(dan): There's probably some races here, we should be changing this in the primary server thread
target := server.clients.Get(nickname) target := server.clients.Get(nickname)
if target != nil && target != client { 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 return false
} }
@ -52,35 +56,35 @@ func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
return true return true
} }
oldnick := NewName(msg.Params[0]) oldnick, oerr := CasefoldName(msg.Params[0])
nickname := NewName(msg.Params[1]) casefoldedNickname, err := CasefoldName(msg.Params[1])
if len(nickname) < 1 { if len(casefoldedNickname) < 1 {
client.Send(nil, server.nameString, ERR_NONICKNAMEGIVEN, client.nickString, "No nickname given") client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
return false return false
} }
if !nickname.IsNickname() { if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen {
client.Send(nil, server.nameString, ERR_ERRONEUSNICKNAME, client.nickString, msg.Params[0], "Erroneous nickname") client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
return false return false
} }
if client.nick == nickname { if client.nick == msg.Params[1] {
return false return false
} }
target := server.clients.Get(oldnick) target := server.clients.Get(oldnick)
if target == nil { 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 return false
} }
//TODO(dan): There's probably some races here, we should be changing this in the primary server thread //TODO(dan): There's probably some races here, we should be changing this in the primary server thread
if server.clients.Get(nickname) != nil { if server.clients.Get(casefoldedNickname) != nil || server.clients.Get(casefoldedNickname) != target {
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 return false
} }
target.ChangeNickname(nickname) target.ChangeNickname(msg.Params[1])
return false return false
} }

View File

@ -73,7 +73,7 @@ func regHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} else if subcommand == "verify" { } else if subcommand == "verify" {
client.Notice("Parsing VERIFY") client.Notice("Parsing VERIFY")
} else { } 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 return false
@ -94,30 +94,30 @@ func removeFailedRegCreateData(store buntdb.DB, account string) {
// regCreateHandler parses the REG CREATE command. // regCreateHandler parses the REG CREATE command.
func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// get and sanitise account name // get and sanitise account name
account := NewName(msg.Params[1]) account := strings.TrimSpace(msg.Params[1])
//TODO(dan): probably don't need explicit check for "*" here... until we actually casemap properly as per rfc7700 casefoldedAccount, err := CasefoldName(account)
if !account.IsNickname() || msg.Params[1] == "*" { // probably don't need explicit check for "*" here... but let's do it anyway just to make sure
client.Send(nil, server.nameString, ERR_REG_UNSPECIFIED_ERROR, client.nickString, msg.Params[1], "Account name is not valid") 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 return false
} }
accountString := account.String()
// check whether account exists // check whether account exists
// do it all in one write tx to prevent races // do it all in one write tx to prevent races
err := server.store.Update(func(tx *buntdb.Tx) error { err = server.store.Update(func(tx *buntdb.Tx) error {
accountKey := fmt.Sprintf(keyAccountExists, accountString) accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
_, err := tx.Get(accountKey) _, err := tx.Get(accountKey)
if err != buntdb.ErrNotFound { 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 //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 return errAccountCreation
} }
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, accountString) registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
tx.Set(accountKey, "1", nil) 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) tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil)
return 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 // account could not be created and relevant numerics have been dispatched, abort
if err != nil { if err != nil {
if err != errAccountCreation { 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()) log.Println("Could not save registration initial data:", err.Error())
} }
return false return false
@ -155,8 +155,8 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
} }
if !callbackValid { if !callbackValid {
client.Send(nil, server.nameString, ERR_REG_INVALID_CALLBACK, client.nickString, msg.Params[1], callbackNamespace, "Callback namespace is not supported") client.Send(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, "Callback namespace is not supported")
removeFailedRegCreateData(server.store, accountString) removeFailedRegCreateData(server.store, casefoldedAccount)
return false return false
} }
@ -170,8 +170,8 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
credentialType = "passphrase" // default from the spec credentialType = "passphrase" // default from the spec
credentialValue = msg.Params[3] credentialValue = msg.Params[3]
} else { } else {
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")
removeFailedRegCreateData(server.store, accountString) removeFailedRegCreateData(server.store, casefoldedAccount)
return false return false
} }
@ -183,14 +183,14 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
} }
} }
if credentialType == "certfp" && client.certfp == "" { 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") client.Send(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, "You are not using a certificiate")
removeFailedRegCreateData(server.store, accountString) removeFailedRegCreateData(server.store, casefoldedAccount)
return false return false
} }
if !credentialValid { if !credentialValid {
client.Send(nil, server.nameString, ERR_REG_INVALID_CRED_TYPE, client.nickString, credentialType, callbackNamespace, "Credential type is not supported") client.Send(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, "Credential type is not supported")
removeFailedRegCreateData(server.store, accountString) removeFailedRegCreateData(server.store, casefoldedAccount)
return false return false
} }
@ -206,7 +206,7 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
return errCertfpAlreadyExists return errCertfpAlreadyExists
} }
tx.Set(assembledKeyCertToAccount, account.String(), nil) tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil)
} }
// make creds // make creds
@ -241,9 +241,9 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
if err == errCertfpAlreadyExists { if err == errCertfpAlreadyExists {
errMsg = "An account already exists for your certificate fingerprint" 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()) log.Println("Could not save registration creds:", err.Error())
removeFailedRegCreateData(server.store, accountString) removeFailedRegCreateData(server.store, casefoldedAccount)
return false return false
} }
@ -259,18 +259,18 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
Clients: []*Client{client}, Clients: []*Client{client},
} }
//TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables //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.account = &account
client.Send(nil, server.nameString, RPL_REGISTRATION_SUCCESS, client.nickString, accountString, "Account created") client.Send(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, account.Name, "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.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf("You are now logged in as %s", account.Name))
client.Send(nil, server.nameString, RPL_SASLSUCCESS, client.nickString, "Authentication successful") client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, "Authentication successful")
return nil return nil
}) })
if err != 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()) log.Println("Could not save verification confirmation (*):", err.Error())
removeFailedRegCreateData(server.store, accountString) removeFailedRegCreateData(server.store, casefoldedAccount)
return false return false
} }

View File

@ -15,7 +15,6 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"regexp"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -27,9 +26,11 @@ import (
// Limits holds the maximum limits for various things such as topic lengths // Limits holds the maximum limits for various things such as topic lengths
type Limits struct { type Limits struct {
AwayLen int AwayLen int
KickLen int ChannelLen int
TopicLen int KickLen int
NickLen int
TopicLen int
} }
type Server struct { type Server struct {
@ -42,10 +43,10 @@ type Server struct {
idle chan *Client idle chan *Client
limits Limits limits Limits
motdLines []string motdLines []string
name Name name string
nameString string // cache for server name string since it's used with almost every reply nameCasefolded string
newConns chan clientConn newConns chan clientConn
operators map[Name][]byte operators map[string][]byte
password []byte password []byte
passwords *PasswordManager passwords *PasswordManager
accountRegistration *AccountRegistration accountRegistration *AccountRegistration
@ -71,6 +72,12 @@ type clientConn struct {
} }
func NewServer(config *Config) *Server { 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{ server := &Server{
accounts: make(map[string]*ClientAccount), accounts: make(map[string]*ClientAccount),
channels: make(ChannelNameMap), channels: make(ChannelNameMap),
@ -79,12 +86,14 @@ func NewServer(config *Config) *Server {
ctime: time.Now(), ctime: time.Now(),
idle: make(chan *Client), idle: make(chan *Client),
limits: Limits{ limits: Limits{
AwayLen: config.Limits.AwayLen, AwayLen: config.Limits.AwayLen,
KickLen: config.Limits.KickLen, ChannelLen: config.Limits.ChannelLen,
TopicLen: config.Limits.TopicLen, KickLen: config.Limits.KickLen,
NickLen: config.Limits.NickLen,
TopicLen: config.Limits.TopicLen,
}, },
name: NewName(config.Server.Name), name: config.Server.Name,
nameString: NewName(config.Server.Name).String(), nameCasefolded: casefoldedName,
newConns: make(chan clientConn), newConns: make(chan clientConn),
operators: config.Operators(), operators: config.Operators(),
signals: make(chan os.Signal, len(SERVER_SIGNALS)), 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 != "" { if config.Server.Password != "" {
server.password = config.Server.PasswordBytes() server.password = config.Server.PasswordBytes()
} }
@ -169,7 +174,7 @@ func NewServer(config *Config) *Server {
// add RPL_ISUPPORT tokens // add RPL_ISUPPORT tokens
server.isupport = NewISupportList() server.isupport = NewISupportList()
server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen)) 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("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("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
server.isupport.Add("CHANTYPES", "#") server.isupport.Add("CHANTYPES", "#")
@ -209,7 +214,7 @@ func loadChannelList(channel *Channel, list string, maskMode ChannelMode) {
if list == "" { if list == "" {
return return
} }
channel.lists[maskMode].AddAll(NewNames(strings.Split(list, " "))) channel.lists[maskMode].AddAll(strings.Split(list, " "))
} }
func (server *Server) loadChannels() { func (server *Server) loadChannels() {
@ -287,8 +292,9 @@ func (server *Server) Run() {
// listen goroutine // listen goroutine
// //
func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) { func (s *Server) listen(addr string, tlsMap map[string]*tls.Config) {
config, listenTLS := tlsMap[NewName(addr)] //TODO(dan): we could casemap this but... eh
config, listenTLS := tlsMap[addr]
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
@ -301,16 +307,16 @@ func (s *Server) listen(addr string, tlsMap map[Name]*tls.Config) {
listener = tls.NewListener(listener, config) listener = tls.NewListener(listener, config)
tlsString = "TLS" 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() { go func() {
for { for {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
Log.error.Printf("%s accept error: %s", s, err) Log.error.Printf("%s accept error: %s", s.name, err)
continue continue
} }
Log.debug.Printf("%s accept: %s", s, conn.RemoteAddr()) Log.debug.Printf("%s accept: %s", s.name, conn.RemoteAddr())
newConn := clientConn{ newConn := clientConn{
Conn: conn, 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) { func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" { if r.Method != "GET" {
Log.error.Printf("%s method not allowed", s) Log.error.Printf("%s method not allowed", s.name)
return return
} }
@ -342,7 +348,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
ws, err := upgrader.Upgrade(w, r, nil) ws, err := upgrader.Upgrade(w, r, nil)
if err != 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 return
} }
@ -360,7 +366,7 @@ func (s *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) {
if listenTLS { if listenTLS {
tlsString = "TLS" 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 { if listenTLS {
err = http.ListenAndServeTLS(addr, config.Cert, config.Key, nil) 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) err = http.ListenAndServe(addr, nil)
} }
if err != 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 // send welcome text
//NOTE(dan): we specifically use the NICK here instead of the nickmask //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 // 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.name, RPL_WELCOME, c.nick, fmt.Sprintf("Welcome to the Internet Relay Network %s", c.nick))
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.name, RPL_YOURHOST, c.nick, fmt.Sprintf("Your host is %s, running version %s", s.name, 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_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 //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() c.RplISupport()
s.MOTD(c) 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) { func (server *Server) MOTD(client *Client) {
if len(server.motdLines) < 1 { 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 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 { 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 return s.name
} }
func (s *Server) String() string { func (s *Server) Nick() string {
return s.name.String()
}
func (s *Server) Nick() Name {
return s.Id() return s.Id()
} }
@ -429,7 +431,7 @@ func (s *Server) Nick() Name {
// PASS <password> // PASS <password>
func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if client.registered { 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 return false
} }
@ -442,8 +444,8 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// check the provided password // check the provided password
password := []byte(msg.Params[0]) password := []byte(msg.Params[0])
if ComparePassword(server.password, password) != nil { if ComparePassword(server.password, password) != nil {
client.Send(nil, server.nameString, ERR_PASSWDMISMATCH, client.nickString, "Password incorrect") client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, "Password incorrect")
client.Send(nil, server.nameString, "ERROR", "Password incorrect") client.Send(nil, server.name, "ERROR", "Password incorrect")
return true return true
} }
@ -454,12 +456,12 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT // PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
clientAddress := IPString(client.socket.conn.RemoteAddr()).String() clientAddress := IPString(client.socket.conn.RemoteAddr())
clientHostname := client.hostname.String() clientHostname := client.hostname
for _, address := range server.proxyAllowedFrom { for _, address := range server.proxyAllowedFrom {
if clientHostname == address || clientAddress == address { if clientHostname == address || clientAddress == address {
client.hostname = LookupHostname(NewName(msg.Params[1])) client.hostname = LookupHostname(msg.Params[1])
return false return false
} }
} }
@ -471,7 +473,7 @@ func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// USER <username> * 0 <realname> // USER <username> * 0 <realname>
func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if client.registered { 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 return false
} }
@ -486,7 +488,8 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// confirm that username is valid // 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") client.Send(nil, "", "ERROR", "Malformed username")
return true return true
} }
@ -499,7 +502,7 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
server.clients.Remove(client) server.clients.Remove(client)
if !client.HasUsername() { if !client.HasUsername() {
client.username = Name("~" + msg.Params[0]) client.username = "~" + msg.Params[0]
client.updateNickMask() client.updateNickMask()
} }
if client.realname == "" { if client.realname == "" {
@ -528,7 +531,7 @@ func quitHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// PING <server1> [<server2>] // PING <server1> [<server2>]
func pingHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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 return false
} }
@ -544,7 +547,7 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// handle JOIN 0 // handle JOIN 0
if msg.Params[0] == "0" { if msg.Params[0] == "0" {
for channel := range client.channels { for channel := range client.channels {
channel.Part(client, client.nickString) channel.Part(client, client.nickCasefolded)
} }
return false return false
} }
@ -556,16 +559,15 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
keys = strings.Split(msg.Params[1], ",") keys = strings.Split(msg.Params[1], ",")
} }
var name Name for i, name := range channels {
for i, nameString := range channels { casefoldedName, err := CasefoldChannel(name)
name = Name(nameString) if err != nil {
if !name.IsChannel() { log.Println("ISN'T CHANNEL NAME:", name)
fmt.Println("ISN'T CHANNEL NAME:", nameString) client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, name, "No such channel")
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, nameString, "No such channel")
continue continue
} }
channel := server.channels.Get(name) channel := server.channels.Get(casefoldedName)
if channel == nil { if channel == nil {
channel = NewChannel(server, name, true) channel = NewChannel(server, name, true)
} }
@ -589,10 +591,11 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
for _, chname := range channels { for _, chname := range channels {
channel := server.channels.Get(Name(chname)) casefoldedChannelName, err := CasefoldChannel(chname)
channel := server.channels.Get(casefoldedChannelName)
if channel == nil { if err != nil || channel == nil {
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel") client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
continue continue
} }
@ -603,9 +606,10 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// TOPIC <channel> [<topic>] // TOPIC <channel> [<topic>]
func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
channel := server.channels.Get(Name(msg.Params[0])) name, err := CasefoldChannel(msg.Params[0])
if channel == nil { channel := server.channels.Get(name)
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 return false
} }
@ -622,26 +626,26 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
message := msg.Params[1] message := msg.Params[1]
var target Name
for _, targetString := range targets { for _, targetString := range targets {
target = Name(targetString) target, err := CasefoldChannel(targetString)
if target.IsChannel() { if err != nil {
channel := server.channels.Get(target) channel := server.channels.Get(target)
if channel == nil { 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 continue
} }
channel.PrivMsg(client, message) channel.PrivMsg(client, message)
} else { } else {
target, err = CasefoldName(targetString)
user := server.clients.Get(target) user := server.clients.Get(target)
if user == nil { if err != nil || user == nil {
client.Send(nil, server.nameString, ERR_NOSUCHNICK, targetString, "No such nick") client.Send(nil, server.name, ERR_NOSUCHNICK, target, "No such nick")
continue continue
} }
user.Send(nil, client.nickMaskString, "PRIVMSG", user.nickString, message) user.Send(nil, client.nickMaskString, "PRIVMSG", user.nick, message)
if user.flags[Away] { if user.flags[Away] {
//TODO(dan): possibly implement cooldown of away notifications to users //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) { if !target.flags[Operator] && channel.flags[Secret] && !channel.members.Has(target) {
continue continue
} }
chstrs = append(chstrs, channel.members[client].Prefixes(isMultiPrefix)+channel.name.String()) chstrs = append(chstrs, channel.members[client].Prefixes(isMultiPrefix)+channel.name)
index++ index++
} }
return chstrs return chstrs
@ -678,9 +682,14 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if client.flags[Operator] { if client.flags[Operator] {
masks := strings.Split(masksString, ",") masks := strings.Split(masksString, ",")
for _, mask := range masks { 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 { 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 continue
} }
for mclient := range matches { for mclient := range matches {
@ -690,31 +699,32 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} else { } else {
// specifically treat this as a single lookup rather than splitting as we do above // specifically treat this as a single lookup rather than splitting as we do above
// this is by design // this is by design
mclient := server.clients.Get(Name(masksString)) casefoldedMask, err := Casefold(masksString)
if mclient == nil { mclient := server.clients.Get(casefoldedMask)
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, masksString, "No such nick") if err != nil || mclient == nil {
client.Send(nil, client.server.name, ERR_NOSUCHNICK, masksString, "No such nick")
// fall through, ENDOFWHOIS is always sent // fall through, ENDOFWHOIS is always sent
} else { } else {
client.getWhoisOf(mclient) 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 return false
} }
func (client *Client) getWhoisOf(target *Client) { 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? //TODO(dan): ...one channel per reply? really?
for _, line := range client.WhoisChannelsNames(target) { 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] { 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) { 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" ) ["*"] [ ( "@" / "+" ) ] // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
@ -734,9 +744,9 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
if channel != nil { if channel != nil {
flags += channel.members[client].Prefixes(target.capabilities[MultiPrefix]) 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) { 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 { func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
friends := client.Friends() friends := client.Friends()
var mask Name var mask string
if len(msg.Params) > 0 { 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? //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 { for _, channel := range server.channels {
whoChannel(client, channel, friends) whoChannel(client, channel, friends)
} }
} else if mask.IsChannel() { } else if mask[0] == '#' {
// TODO implement wildcard matching // TODO implement wildcard matching
//TODO(dan): ^ only for opers //TODO(dan): ^ only for opers
channel := server.channels.Get(mask) 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 return false
} }
// OPER <name> <password> // OPER <name> <password>
func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { 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] hash := server.operators[name]
password := []byte(msg.Params[1]) password := []byte(msg.Params[1])
err := ComparePassword(hash, password) err = ComparePassword(hash, password)
if (hash == nil) || (err != nil) { 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 return true
} }
client.flags[Operator] = 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? //TODO(dan): Should this be sent automagically as part of setting the flag/mode?
modech := ModeChanges{&ModeChange{ modech := ModeChanges{&ModeChange{
mode: Operator, mode: Operator,
op: Add, op: Add,
}} }}
client.Send(nil, server.nameString, "MODE", client.nickString, modech.String()) client.Send(nil, server.name, "MODE", client.nick, modech.String())
return false return false
} }
@ -830,17 +849,17 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var op ModeOp var op ModeOp
if client.flags[Away] { if client.flags[Away] {
op = Add 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 { } else {
op = Remove 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? //TODO(dan): Should this be sent automagically as part of setting the flag/mode?
modech := ModeChanges{&ModeChange{ modech := ModeChanges{&ModeChange{
mode: Away, mode: Away,
op: op, 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 // dispatch away-notify
for friend := range client.Friends() { 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 { func isonHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var nicks = msg.Params var nicks = msg.Params
var err error
var casefoldedNick string
ison := make([]string, 0) ison := make([]string, 0)
for _, nick := range nicks { for _, nick := range nicks {
if iclient := server.clients.Get(Name(nick)); iclient != nil { casefoldedNick, err = CasefoldName(nick)
ison = append(ison, iclient.Nick().String()) 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 return false
} }
@ -886,10 +911,9 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
message := msg.Params[1] message := msg.Params[1]
var target Name
for _, targetString := range targets { for _, targetString := range targets {
target = Name(targetString) target, cerr := CasefoldChannel(targetString)
if target.IsChannel() { if cerr == nil {
channel := server.channels.Get(target) channel := server.channels.Get(target)
if channel == nil { if channel == nil {
// errors silently ignored with NOTICE as per RFC // 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) channel.PrivMsg(client, message)
} else { } else {
target, err := CasefoldName(targetString)
if err != nil {
continue
}
user := server.clients.Get(target) user := server.clients.Get(target)
if user == nil { if user == nil {
// errors silently ignored with NOTICE as per RFC // errors silently ignored with NOTICE as per RFC
continue continue
} }
user.Send(nil, client.nickMaskString, "NOTICE", user.nickString, message) user.Send(nil, client.nickMaskString, "NOTICE", user.nick, message)
} }
} }
return false return false
@ -913,7 +942,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
channels := strings.Split(msg.Params[0], ",") channels := strings.Split(msg.Params[0], ",")
users := strings.Split(msg.Params[1], ",") users := strings.Split(msg.Params[1], ",")
if (len(channels) != len(users)) && (len(users) != 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 return false
} }
@ -931,15 +960,17 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
comment = msg.Params[2] comment = msg.Params[2]
} }
for chname, nickname := range kicks { for chname, nickname := range kicks {
channel := server.channels.Get(Name(chname)) casefoldedChname, err := CasefoldChannel(chname)
if channel == nil { channel := server.channels.Get(casefoldedChname)
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 continue
} }
target := server.clients.Get(Name(nickname)) casefoldedNickname, err := CasefoldName(nickname)
if target == nil { target := server.clients.Get(casefoldedNickname)
client.Send(nil, server.nameString, ERR_NOSUCHNICK, nickname, "No such nick") if err != nil || target == nil {
client.Send(nil, server.name, ERR_NOSUCHNICK, nickname, "No such nick")
continue continue
} }
@ -968,7 +999,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
channel.Kick(client, target, comment) channel.Kick(client, target, comment)
} else { } 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 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): target server when we have multiple servers
//TODO(dan): we should continue just fine if it's this current server though //TODO(dan): we should continue just fine if it's this current server though
if target != "" { 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 return false
} }
@ -1001,15 +1032,16 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
} else { } else {
for _, chname := range channels { for _, chname := range channels {
channel := server.channels.Get(Name(chname)) casefoldedChname, err := CasefoldChannel(chname)
if channel == nil || (!client.flags[Operator] && channel.flags[Secret]) { channel := server.channels.Get(casefoldedChname)
client.Send(nil, server.nameString, ERR_NOSUCHCHANNEL, client.nickString, chname, "No such channel") 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 continue
} }
client.RplList(channel) 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 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>}] // NAMES [<channel>{,<channel>}]
@ -1048,9 +1080,10 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
for _, chname := range channels { for _, chname := range channels {
channel := server.channels.Get(Name(chname)) casefoldedChname, err := CasefoldChannel(chname)
if channel == nil { channel := server.channels.Get(casefoldedChname)
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 continue
} }
channel.Names(client) channel.Names(client)
@ -1064,12 +1097,13 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
if len(msg.Params) > 0 { if len(msg.Params) > 0 {
target = msg.Params[0] target = msg.Params[0]
} }
if (target != "") && (Name(target) != server.name) { casefoldedTarget, err := Casefold(target)
client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server") if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) {
client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
return false 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() client.RplISupport()
return false return false
} }
@ -1079,16 +1113,18 @@ func inviteHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
nickname := msg.Params[0] nickname := msg.Params[0]
channelName := msg.Params[1] channelName := msg.Params[1]
target := server.clients.Get(Name(nickname)) casefoldedNickname, err := CasefoldName(nickname)
if target == nil { target := server.clients.Get(casefoldedNickname)
client.Send(nil, server.nameString, ERR_NOSUCHNICK, client.nickString, nickname, "No such nick") if err != nil || target == nil {
client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, nickname, "No such nick")
return false return false
} }
channel := server.channels.Get(Name(channelName)) casefoldedChannelName, err := CasefoldChannel(channelName)
if channel == nil { channel := server.channels.Get(casefoldedChannelName)
client.Send(nil, server.nameString, RPL_INVITING, client.nickString, target.nickString, channelName) if err != nil || channel == nil {
target.Send(nil, client.nickMaskString, "INVITE", target.nickString, channel.nameString) client.Send(nil, server.name, RPL_INVITING, client.nick, target.nick, channelName)
target.Send(nil, client.nickMaskString, "INVITE", target.nick, channel.name)
return true return true
} }
@ -1102,11 +1138,12 @@ func timeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if len(msg.Params) > 0 { if len(msg.Params) > 0 {
target = msg.Params[0] target = msg.Params[0]
} }
if (target != "") && (Name(target) != server.name) { casefoldedTarget, err := Casefold(target)
client.Send(nil, server.nameString, ERR_NOSUCHSERVER, client.nickString, target, "No such server") if (target != "") && err != nil || (casefoldedTarget != server.nameCasefolded) {
client.Send(nil, server.name, ERR_NOSUCHSERVER, client.nick, target, "No such server")
return false 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 return false
} }
@ -1118,13 +1155,14 @@ func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
comment = msg.Params[1] comment = msg.Params[1]
} }
target := server.clients.Get(Name(nickname)) casefoldedNickname, err := CasefoldName(nickname)
if target == nil { target := server.clients.Get(casefoldedNickname)
client.Send(nil, client.server.nameString, ERR_NOSUCHNICK, nickname, "No such nick") if err != nil || target == nil {
client.Send(nil, client.server.name, ERR_NOSUCHNICK, nickname, "No such nick")
return false return false
} }
quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nickString, comment) quitMsg := fmt.Sprintf("Killed (%s (%s))", client.nick, comment)
target.Quit(quitMsg) target.Quit(quitMsg)
target.destroy() target.destroy()
return false return false
@ -1143,15 +1181,15 @@ func whowasHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
// target = msg.Params[2] // target = msg.Params[2]
//} //}
for _, nickname := range nicknames { for _, nickname := range nicknames {
results := server.whoWas.Find(Name(nickname), count) results := server.whoWas.Find(nickname, count)
if len(results) == 0 { 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 { } else {
for _, whoWas := range results { 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 return false
} }

View File

@ -6,91 +6,71 @@
package irc package irc
import ( import (
"regexp" "errors"
"strings" "strings"
"golang.org/x/text/unicode/norm" "golang.org/x/text/secure/precis"
) )
var ( var (
// regexps errInvalidCharacter = errors.New("Invalid character")
// these get replaced with real regexes at server load time
ChannelNameExpr = regexp.MustCompile("^$")
NicknameExpr = regexp.MustCompile("^$")
) )
// Names are normalized and canonicalized to remove formatting marks // Casefold returns a casefolded string, without doing any name or channel character checks.
// and simplify usage. They are things like hostnames and usermasks. func Casefold(str string) (string, error) {
type Name string return precis.Nickname.String(str)
func NewName(str string) Name {
return Name(norm.NFKC.String(str))
} }
func NewNames(strs []string) []Name { // CasefoldChannel returns a casefolded version of a channel name.
names := make([]Name, len(strs)) func CasefoldChannel(name string) (string, error) {
for index, str := range strs { lowered, err := precis.Nickname.String(name)
names[index] = NewName(str)
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 { if err != nil {
return ChannelNameExpr.MatchString(name.String()) return "", err
} }
func (name Name) IsNickname() bool { // space can't be used
namestr := name.String() // , is used as a separator
// * is used for unregistered clients // * is used in mask matching
// * is used for mask matching // ? is used in mask matching
// ? is used for mask matching // . denotes a server name
// . is used to denote server names // ! separates nickname from username
// , is used as a separator by the protocol // @ separates username from hostname
// ! separates username from nickname // : means trailing
// @ separates nick+user from hostname
// # is a channel prefix // # is a channel prefix
// ~&@%+ are channel membership prefixes // ~&@%+ are channel membership prefixes
// - is typically disallowed from first char of nicknames // - I feel like disallowing
// nicknames can't start with digits if strings.Contains(lowered, " ") || strings.Contains(lowered, ",") ||
if strings.Contains(namestr, "*") || strings.Contains(namestr, "?") || strings.Contains(lowered, "*") || strings.Contains(lowered, "?") ||
strings.Contains(namestr, ".") || strings.Contains(namestr, ",") || strings.Contains(lowered, ".") || strings.Contains(lowered, "!") ||
strings.Contains(namestr, "!") || strings.Contains(namestr, "@") || strings.Contains(lowered, "@") ||
strings.Contains("#~&@%+-1234567890", string(namestr[0])) { strings.Contains("#~&@%+-", string(lowered[0])) {
return false return "", errInvalidCharacter
} }
// names that look like hostnames are restricted to servers, as with other ircds
if IsHostname(namestr) { return lowered, err
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)
} }

View File

@ -13,25 +13,29 @@ import (
// simple types // simple types
// //
type ChannelNameMap map[Name]*Channel type ChannelNameMap map[string]*Channel
func (channels ChannelNameMap) Get(name Name) *Channel { func (channels ChannelNameMap) Get(name string) *Channel {
return channels[name.ToLower()] name, err := CasefoldChannel(name)
if err == nil {
return channels[name]
}
return nil
} }
func (channels ChannelNameMap) Add(channel *Channel) error { 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) return fmt.Errorf("%s: already set", channel.name)
} }
channels[channel.name.ToLower()] = channel channels[channel.nameCasefolded] = channel
return nil return nil
} }
func (channels ChannelNameMap) Remove(channel *Channel) error { 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) return fmt.Errorf("%s: mismatch", channel.name)
} }
delete(channels, channel.name.ToLower()) delete(channels, channel.nameCasefolded)
return nil return nil
} }
@ -118,6 +122,6 @@ func (channels ChannelSet) First() *Channel {
// //
type Identifiable interface { type Identifiable interface {
Id() Name Id() string
Nick() Name Nick() string
} }

View File

@ -1,4 +1,5 @@
// Copyright (c) 2012-2014 Jeremy Latt // Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license // released under the MIT license
package irc package irc
@ -10,10 +11,11 @@ type WhoWasList struct {
} }
type WhoWas struct { type WhoWas struct {
nickname Name nicknameCasefolded string
username Name nickname string
hostname Name username string
realname string hostname string
realname string
} }
func NewWhoWasList(size uint) *WhoWasList { func NewWhoWasList(size uint) *WhoWasList {
@ -24,10 +26,11 @@ func NewWhoWasList(size uint) *WhoWasList {
func (list *WhoWasList) Append(client *Client) { func (list *WhoWasList) Append(client *Client) {
list.buffer[list.end] = &WhoWas{ list.buffer[list.end] = &WhoWas{
nickname: client.Nick(), nicknameCasefolded: client.nickCasefolded,
username: client.username, nickname: client.nick,
hostname: client.hostname, username: client.username,
realname: client.realname, hostname: client.hostname,
realname: client.realname,
} }
list.end = (list.end + 1) % len(list.buffer) list.end = (list.end + 1) % len(list.buffer)
if list.end == list.start { 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) results := make([]*WhoWas, 0)
casefoldedNickname, err := CasefoldName(nickname)
if err != nil {
return results
}
for whoWas := range list.Each() { for whoWas := range list.Each() {
if nickname != whoWas.nickname { if casefoldedNickname != whoWas.nicknameCasefolded {
continue continue
} }
results = append(results, whoWas) results = append(results, whoWas)