3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-25 13:29:27 +01:00

Another massive refactor to support mutliple connections per nick.

This commit is contained in:
Jeremy Latt 2012-12-15 14:34:20 -08:00
parent 559445d9a8
commit f2aedbaffd
11 changed files with 636 additions and 443 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
pkg pkg
bin bin
ergonomadic src/code.google.com/

View File

@ -1,2 +1,4 @@
#!/bin/bash #!/bin/bash
env GOPATH="$PWD" go install -v ergonomadic export GOPATH="$PWD"
go get "code.google.com/p/go.crypto/bcrypt"
go install -v ergonomadic genpasswd

View File

@ -8,6 +8,6 @@ import (
func main() { func main() {
name := flag.String("name", "localhost", "A name for the server") name := flag.String("name", "localhost", "A name for the server")
listen := flag.String("listen", ":6667", "interface to listen on") listen := flag.String("listen", ":6667", "interface to listen on")
flag.Parse() flag.Parse()
irc.NewServer(*name).Listen(*listen) irc.NewServer(*name).Listen(*listen)
} }

View File

@ -0,0 +1,18 @@
package main
import (
"code.google.com/p/go.crypto/bcrypt"
"encoding/base64"
"flag"
"fmt"
)
func main() {
flag.Parse()
password := flag.Arg(0)
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(hash))
}

View File

@ -2,33 +2,82 @@ package irc
type Channel struct { type Channel struct {
server *Server server *Server
replies chan<- Reply
commands chan<- ChannelCommand
name string name string
key string key string
topic string topic string
members ClientSet members UserSet
noOutside bool noOutside bool
password string password string
} }
type ChannelSet map[*Channel]bool type ChannelSet map[*Channel]bool
func (set ChannelSet) Add(channel *Channel) {
set[channel] = true
}
func (set ChannelSet) Remove(channel *Channel) {
delete(set, channel)
}
type ChannelCommand interface {
Command
HandleChannel(channel *Channel)
}
type JoinChannelCommand struct {
*JoinCommand
key string
}
type PartChannelCommand struct {
Command
message string
}
type GetTopicChannelCommand struct {
*TopicCommand
}
type SetTopicChannelCommand struct {
*TopicCommand
}
type PrivMsgChannelCommand struct {
*PrivMsgCommand
}
// NewChannel creates a new channel from a `Server` and a `name` string, which // NewChannel creates a new channel from a `Server` and a `name` string, which
// must be unique on the server. // must be unique on the server.
func NewChannel(s *Server, name string) *Channel { func NewChannel(s *Server, name string) *Channel {
return &Channel{ replies := make(chan Reply)
name: name, commands := make(chan ChannelCommand)
members: make(ClientSet), channel := &Channel{
server: s, name: name,
members: make(UserSet),
server: s,
commands: commands,
replies: replies,
}
go channel.receiveReplies(replies)
go channel.receiveCommands(commands)
return channel
}
// Forward `Reply`s to all `User`s of the `Channel`.
func (ch *Channel) receiveReplies(replies <-chan Reply) {
for reply := range replies {
for client := range ch.members {
client.replies <- reply
}
} }
} }
// Send a `Reply` to all `Client`s of the `Channel`. Skip `fromClient`, if it is func (ch *Channel) receiveCommands(commands <-chan ChannelCommand) {
// provided. for command := range commands {
func (ch *Channel) Send(reply Reply, fromClient *Client) { command.HandleChannel(ch)
for client := range ch.members {
if client != fromClient {
client.send <- reply
}
} }
} }
@ -47,72 +96,86 @@ func (ch *Channel) IsEmpty() bool {
} }
// //
// channel functionality // commands
// //
func (ch *Channel) Join(cl *Client, key string) { func (m *JoinChannelCommand) HandleChannel(channel *Channel) {
if ch.key != key { client := m.Client()
cl.send <- ErrBadChannelKey(ch) user := client.user
if channel.key != m.key {
client.user.replies <- ErrBadChannelKey(channel)
return return
} }
ch.members[cl] = true channel.members.Add(client.user)
cl.channels[ch] = true client.user.channels.Add(channel)
ch.Send(RplJoin(ch, cl), nil) channel.replies <- RplJoin(channel, user)
ch.GetTopic(cl) channel.GetTopic(user)
cl.send <- RplNamReply(ch) client.user.replies <- RplNamReply(channel)
cl.send <- RplEndOfNames(ch.server) client.user.replies <- RplEndOfNames(channel.server)
} }
func (ch *Channel) Part(cl *Client, message string) { func (m *PartChannelCommand) HandleChannel(channel *Channel) {
if !ch.members[cl] { user := m.Client().user
cl.send <- ErrNotOnChannel(ch)
if !channel.members[user] {
user.replies <- ErrNotOnChannel(channel)
return return
} }
if message == "" { msg := m.message
message = cl.Nick() if msg == "" {
msg = user.Nick()
} }
ch.Send(RplPart(ch, cl, message), nil) channel.replies <- RplPart(channel, user, msg)
delete(ch.members, cl) channel.members.Remove(user)
delete(cl.channels, ch) user.channels.Remove(channel)
if len(ch.members) == 0 { if len(channel.members) == 0 {
ch.server.DeleteChannel(ch) channel.server.DeleteChannel(channel)
} }
} }
func (ch *Channel) PrivMsg(cl *Client, message string) { func (channel *Channel) GetTopic(user *User) {
ch.Send(RplPrivMsgChannel(ch, cl, message), cl) if !channel.members[user] {
} user.replies <- ErrNotOnChannel(channel)
func (ch *Channel) GetTopic(cl *Client) {
if !ch.members[cl] {
cl.send <- ErrNotOnChannel(ch)
return return
} }
if ch.topic != "" { if channel.topic == "" {
cl.send <- RplTopic(ch) user.replies <- RplNoTopic(channel)
} else {
cl.send <- RplNoTopic(ch)
}
}
func (ch *Channel) ChangeTopic(cl *Client, newTopic string) {
if !ch.members[cl] {
cl.send <- ErrNotOnChannel(ch)
return return
} }
ch.topic = newTopic user.replies <- RplTopic(channel)
}
if ch.topic != "" {
ch.Send(RplTopic(ch), nil) func (m *GetTopicChannelCommand) HandleChannel(channel *Channel) {
} else { channel.GetTopic(m.Client().user)
ch.Send(RplNoTopic(ch), nil) }
}
func (m *SetTopicChannelCommand) HandleChannel(channel *Channel) {
user := m.Client().user
if !channel.members[user] {
user.replies <- ErrNotOnChannel(channel)
return
}
channel.topic = m.topic
if channel.topic == "" {
channel.replies <- RplNoTopic(channel)
return
}
channel.replies <- RplTopic(channel)
}
func (m *PrivMsgChannelCommand) HandleChannel(channel *Channel) {
channel.replies <- RplPrivMsgChannel(channel, m.Client().user, m.message)
} }

View File

@ -9,7 +9,7 @@ import (
type Client struct { type Client struct {
conn net.Conn conn net.Conn
send chan<- Reply replies chan<- Reply
username string username string
realname string realname string
hostname string hostname string
@ -17,10 +17,9 @@ type Client struct {
serverPass bool serverPass bool
registered bool registered bool
away bool away bool
wallOps bool
server *Server server *Server
channels ChannelSet
atime time.Time atime time.Time
user *User
} }
type ClientSet map[*Client]bool type ClientSet map[*Client]bool
@ -28,21 +27,20 @@ type ClientSet map[*Client]bool
func NewClient(server *Server, conn net.Conn) *Client { func NewClient(server *Server, conn net.Conn) *Client {
read := StringReadChan(conn) read := StringReadChan(conn)
write := StringWriteChan(conn) write := StringWriteChan(conn)
send := make(chan Reply) replies := make(chan Reply)
client := &Client{ client := &Client{
channels: make(ChannelSet),
conn: conn, conn: conn,
hostname: LookupHostname(conn.RemoteAddr()), hostname: LookupHostname(conn.RemoteAddr()),
server: server, server: server,
send: send, replies: replies,
} }
// Connect the conn to the server. // Connect the conn to the server.
go client.readConn(read) go client.readConn(read)
// Connect the reply channel to the conn. // Connect the reply channel to the conn.
go client.writeConn(write, send) go client.writeConn(write, replies)
return client return client
} }
@ -51,19 +49,19 @@ func (c *Client) readConn(recv <-chan string) {
for str := range recv { for str := range recv {
log.Printf("%s > %s", c.Id(), str) log.Printf("%s > %s", c.Id(), str)
m, err := ParseMessage(str) m, err := ParseCommand(str)
if err != nil { if err != nil {
// TODO handle error // TODO handle error
continue continue
} }
m.SetClient(c) m.SetClient(c)
c.server.recv <- m c.server.commands <- m
} }
} }
func (c *Client) writeConn(write chan<- string, send <-chan Reply) { func (c *Client) writeConn(write chan<- string, replies <-chan Reply) {
for reply := range send { for reply := range replies {
replyStr := reply.String(c) replyStr := reply.String(c)
log.Printf("%s < %s", c.Id(), replyStr) log.Printf("%s < %s", c.Id(), replyStr)
write <- replyStr write <- replyStr
@ -71,16 +69,18 @@ func (c *Client) writeConn(write chan<- string, send <-chan Reply) {
} }
func (c *Client) Nick() string { func (c *Client) Nick() string {
if c.user != nil {
return c.user.nick
}
if c.nick != "" { if c.nick != "" {
return c.nick return c.nick
} }
return "*" return "*"
} }
func (c *Client) UModeString() string { func (c *Client) UModeString() string {
if c.wallOps {
return "+w"
}
return "" return ""
} }
@ -106,3 +106,7 @@ func (c *Client) UserHost() string {
func (c *Client) Id() string { func (c *Client) Id() string {
return c.UserHost() return c.UserHost()
} }
func (c *Client) PublicId() string {
return fmt.Sprintf("%s!%s@%s", c.Nick(), c.Nick(), c.server.Id())
}

View File

@ -2,63 +2,66 @@ package irc
import ( import (
"errors" "errors"
"fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
type Message interface { type Command interface {
Handle(s *Server, c *Client)
Client() *Client Client() *Client
SetClient(c *Client) SetClient(*Client)
Handle(*Server)
} }
var ( var (
NotEnoughArgsError = errors.New("not enough arguments") NotEnoughArgsError = errors.New("not enough arguments")
UModeUnknownFlagError = errors.New("unknown umode flag")
) )
type BaseMessage struct { type BaseCommand struct {
client *Client client *Client
} }
func (m *BaseMessage) Client() *Client { func (base *BaseCommand) Client() *Client {
return m.client return base.client
} }
func (m *BaseMessage) SetClient(c *Client) { func (base *BaseCommand) SetClient(c *Client) {
m.client = c base.client = c
} }
// unknown <command> [args...] // unknown <command> [args...]
type UnknownMessage struct { type UnknownCommand struct {
*BaseMessage *BaseCommand
command string command string
args []string args []string
} }
// NB: no constructor, created on demand in parser for invalid messages. func NewUnknownCommand(command string, args []string) Command {
return &UnknownCommand{
BaseCommand: &BaseCommand{},
command: command,
args: args,
}
}
func (m *UnknownMessage) Handle(s *Server, c *Client) { func (m *UnknownCommand) Handle(s *Server) {
c.send <- ErrUnknownCommand(s, m.command) m.Client().replies <- ErrUnknownCommand(s, m.command)
} }
// PING <server1> [ <server2> ] // PING <server1> [ <server2> ]
type PingMessage struct { type PingCommand struct {
*BaseMessage *BaseCommand
server string server string
server2 string server2 string
} }
func NewPingMessage(args []string) (Message, error) { func NewPingCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &PingMessage{ msg := &PingCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
server: args[0], server: args[0],
} }
if len(args) > 1 { if len(args) > 1 {
@ -67,95 +70,78 @@ func NewPingMessage(args []string) (Message, error) {
return msg, nil return msg, nil
} }
func (m *PingMessage) Handle(s *Server, c *Client) {
c.send <- RplPong(s)
}
// PONG <server> [ <server2> ] // PONG <server> [ <server2> ]
type PongMessage struct { type PongCommand struct {
*BaseMessage *BaseCommand
server1 string server1 string
server2 string server2 string
} }
func NewPongMessage(args []string) (Message, error) { func NewPongCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
message := &PongMessage{server1: args[0]} message := &PongCommand{
BaseCommand: &BaseCommand{},
server1: args[0],
}
if len(args) > 1 { if len(args) > 1 {
message.server2 = args[1] message.server2 = args[1]
} }
return message, nil return message, nil
} }
func (m *PongMessage) Handle(s *Server, c *Client) {
// no-op
}
// PASS <password> // PASS <password>
type PassMessage struct { type PassCommand struct {
*BaseMessage *BaseCommand
password string password string
} }
func NewPassMessage(args []string) (Message, error) { func NewPassCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &PassMessage{ return &PassCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
password: args[0], password: args[0],
}, nil }, nil
} }
func (m *PassMessage) Handle(s *Server, c *Client) {
if m.password == s.password {
c.serverPass = true
} else {
c.send <- ErrPasswdMismatch(s)
}
}
// NICK <nickname> // NICK <nickname>
type NickMessage struct { type NickCommand struct {
*BaseMessage *BaseCommand
nickname string nickname string
} }
func NewNickMessage(args []string) (Message, error) { func NewNickCommand(args []string) (Command, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &NickMessage{ return &NickCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
nickname: args[0], nickname: args[0],
}, nil }, nil
} }
func (m *NickMessage) Handle(s *Server, c *Client) {
s.ChangeNick(c, m.nickname)
}
// USER <user> <mode> <unused> <realname> // USER <user> <mode> <unused> <realname>
type UserMessage struct { type UserCommand struct {
*BaseMessage *BaseCommand
user string user string
mode uint8 mode uint8
unused string unused string
realname string realname string
} }
func NewUserMessage(args []string) (Message, error) { func NewUserCommand(args []string) (Command, error) {
if len(args) != 4 { if len(args) != 4 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &UserMessage{ msg := &UserCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
user: args[0], user: args[0],
unused: args[2], unused: args[2],
realname: args[3], realname: args[3],
@ -167,20 +153,16 @@ func NewUserMessage(args []string) (Message, error) {
return msg, nil return msg, nil
} }
func (m *UserMessage) Handle(s *Server, c *Client) { // QUIT [ <Quit Command> ]
s.UserLogin(c, m.user, m.realname)
}
// QUIT [ <Quit Message> ] type QuitCommand struct {
*BaseCommand
type QuitMessage struct {
*BaseMessage
message string message string
} }
func NewQuitMessage(args []string) (Message, error) { func NewQuitCommand(args []string) (Command, error) {
msg := &QuitMessage{ msg := &QuitCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
} }
if len(args) > 0 { if len(args) > 0 {
msg.message = args[0] msg.message = args[0]
@ -188,98 +170,18 @@ func NewQuitMessage(args []string) (Message, error) {
return msg, nil return msg, nil
} }
func (m *QuitMessage) Handle(s *Server, c *Client) {
s.Quit(c, m.message)
}
// MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
type ModeMessage struct {
*BaseMessage
nickname string
modes []string
}
type ChannelModeMessage struct {
*ModeMessage
channel string
modeParams []string
}
// mode s is accepted but ignored, like some other modes
var MODE_RE = regexp.MustCompile("^[-+][iwroOs]+$")
var CHANNEL_RE = regexp.MustCompile("^[+\\&\\!#][:alnum:]+$")
var EXTRACT_MODE_RE = regexp.MustCompile("^([-+])?([aimnqpsrtklbeI]+)$")
func NewModeMessage(args []string) (Message, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
}
if (len(args) > 1) && CHANNEL_RE.MatchString(args[1]) {
cmsg := new(ChannelModeMessage)
cmsg.nickname = args[0]
if len(args) > 2 {
groups := EXTRACT_MODE_RE.FindStringSubmatch(args[2])
cmsg.modes = make([]string, len(groups[2]))
i := 0
for _, char := range groups[2] {
cmsg.modes[i] = fmt.Sprintf("%s%c", groups[1], char)
i++
}
}
if len(args) > 3 {
cmsg.modeParams = strings.Split(args[3], ",")
}
return cmsg, nil
}
msg := &ModeMessage{
BaseMessage: &BaseMessage{},
nickname: args[0],
}
for _, arg := range args[1:] {
if !MODE_RE.MatchString(arg) {
return nil, UModeUnknownFlagError
}
prefix := arg[0]
for _, c := range arg[1:] {
mode := fmt.Sprintf("%c%c", prefix, c)
msg.modes = append(msg.modes, mode)
}
}
return msg, nil
}
func (m *ModeMessage) Handle(s *Server, c *Client) {
if m.nickname != c.nick {
c.send <- ErrUsersDontMatch(s)
return
}
s.ChangeUserMode(c, m.modes)
}
func (m *ChannelModeMessage) Handle(s *Server, c *Client) {
channel := s.channels[m.channel]
if channel != nil {
c.send <- ErrNoChanModes(channel)
} else {
c.send <- ErrNoSuchChannel(s, m.channel)
}
}
// JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0" // JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
type JoinMessage struct { type JoinCommand struct {
*BaseMessage *BaseCommand
channels []string channels []string
keys []string keys []string
zero bool zero bool
} }
func NewJoinMessage(args []string) (Message, error) { func NewJoinCommand(args []string) (Command, error) {
msg := &JoinMessage{ msg := &JoinCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
} }
if len(args) > 0 { if len(args) > 0 {
if args[0] == "0" { if args[0] == "0" {
@ -295,38 +197,20 @@ func NewJoinMessage(args []string) (Message, error) {
return msg, nil return msg, nil
} }
func (m *JoinMessage) Handle(s *Server, c *Client) { // PART <channel> *( "," <channel> ) [ <Part Command> ]
if m.zero {
for channel := range c.channels {
channel.Part(c, "")
}
} else {
for i, name := range m.channels {
key := ""
if len(m.keys) > i {
key = m.keys[i]
}
s.GetOrMakeChannel(name).Join(c, key) type PartCommand struct {
} *BaseCommand
}
}
// PART <channel> *( "," <channel> ) [ <Part Message> ]
type PartMessage struct {
*BaseMessage
channels []string channels []string
message string message string
} }
func NewPartMessage(args []string) (Message, error) { func NewPartCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &PartMessage{ msg := &PartCommand{
BaseCommand: &BaseCommand{},
BaseMessage: &BaseMessage{},
channels: strings.Split(args[0], ","), channels: strings.Split(args[0], ","),
} }
if len(args) > 1 { if len(args) > 1 {
@ -335,39 +219,26 @@ func NewPartMessage(args []string) (Message, error) {
return msg, nil return msg, nil
} }
func (m *PartMessage) Handle(s *Server, c *Client) {
for _, chname := range m.channels {
channel := s.channels[chname]
if channel == nil {
c.send <- ErrNoSuchChannel(s, chname)
continue
}
channel.Part(c, m.message)
}
}
// PRIVMSG <target> <message> // PRIVMSG <target> <message>
type PrivMsgMessage struct { type PrivMsgCommand struct {
*BaseMessage *BaseCommand
target string target string
message string message string
} }
func NewPrivMsgMessage(args []string) (Message, error) { func NewPrivMsgCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &PrivMsgMessage{ return &PrivMsgCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
target: args[0], target: args[0],
message: args[1], message: args[1],
}, nil }, nil
} }
func (m *PrivMsgMessage) TargetIsChannel() bool { func (m *PrivMsgCommand) TargetIsChannel() bool {
switch m.target[0] { switch m.target[0] {
case '&', '#', '+', '!': case '&', '#', '+', '!':
return true return true
@ -375,35 +246,20 @@ func (m *PrivMsgMessage) TargetIsChannel() bool {
return false return false
} }
func (m *PrivMsgMessage) Handle(s *Server, c *Client) {
if m.TargetIsChannel() {
if channel := s.channels[m.target]; channel != nil {
channel.PrivMsg(c, m.message)
return
}
} else {
if client := s.nicks[m.target]; client != nil {
client.send <- RplPrivMsg(c, client, m.message)
return
}
}
c.send <- ErrNoSuchNick(s, m.target)
}
// TOPIC [newtopic] // TOPIC [newtopic]
type TopicMessage struct { type TopicCommand struct {
*BaseMessage *BaseCommand
channel string channel string
topic string topic string
} }
func NewTopicMessage(args []string) (Message, error) { func NewTopicCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &TopicMessage{ msg := &TopicCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
channel: args[0], channel: args[0],
} }
if len(args) > 1 { if len(args) > 1 {
@ -412,38 +268,40 @@ func NewTopicMessage(args []string) (Message, error) {
return msg, nil return msg, nil
} }
func (m *TopicMessage) Handle(s *Server, c *Client) {
channel := s.channels[m.channel]
if channel == nil {
c.send <- ErrNoSuchChannel(s, m.channel)
return
}
if m.topic == "" {
channel.GetTopic(c)
} else {
channel.ChangeTopic(c, m.topic)
}
}
// LOGIN <nick> <password> // LOGIN <nick> <password>
type LoginMessage struct { type LoginCommand struct {
*BaseMessage *BaseCommand
nick string nick string
password string password string
} }
func NewLoginMessage(args []string) (Message, error) { func NewLoginCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &LoginMessage{ return &LoginCommand{
BaseMessage: &BaseMessage{}, BaseCommand: &BaseCommand{},
nick: args[0], nick: args[0],
password: args[1], password: args[1],
}, nil }, nil
} }
func (m *LoginMessage) Handle(s *Server, c *Client) { // RESERVE <nick> <password>
// TODO
type ReserveCommand struct {
*BaseCommand
nick string
password string
}
func NewReserveCommand(args []string) (Command, error) {
if len(args) < 2 {
return nil, NotEnoughArgsError
}
return &ReserveCommand{
BaseCommand: &BaseCommand{},
nick: args[0],
password: args[1],
}, nil
} }

View File

@ -5,35 +5,30 @@ import (
"strings" "strings"
) )
type ParseFunc func([]string) (Message, error) type ParseFunc func([]string) (Command, error)
var ( var (
ErrParseMessage = errors.New("failed to parse message") ErrParseCommand = errors.New("failed to parse message")
parseCommandFuncs = map[string]ParseFunc{ parseCommandFuncs = map[string]ParseFunc{
"JOIN": NewJoinMessage, "JOIN": NewJoinCommand,
"MODE": NewModeMessage, "LOGIN": NewLoginCommand,
"LOGIN": NewLoginMessage, "NICK": NewNickCommand,
"NICK": NewNickMessage, "PART": NewPartCommand,
"PART": NewPartMessage, "PASS": NewPassCommand,
"PASS": NewPassMessage, "PING": NewPingCommand,
"PING": NewPingMessage, "PONG": NewPongCommand,
"PONG": NewPongMessage, "PRIVMSG": NewPrivMsgCommand,
"PRIVMSG": NewPrivMsgMessage, "QUIT": NewQuitCommand,
"QUIT": NewQuitMessage, "TOPIC": NewTopicCommand,
"TOPIC": NewTopicMessage, "USER": NewUserCommand,
"USER": NewUserMessage,
} }
) )
func ParseMessage(line string) (Message, error) { func ParseCommand(line string) (Command, error) {
command, args := parseLine(line) command, args := parseLine(line)
constructor, ok := parseCommandFuncs[command] constructor := parseCommandFuncs[command]
if !ok { if constructor == nil {
return &UnknownMessage{ return NewUnknownCommand(command, args), nil
BaseMessage: &BaseMessage{},
command: command,
args: args,
}, nil
} }
return constructor(args) return constructor(args)
} }

View File

@ -8,6 +8,7 @@ import (
type Identifier interface { type Identifier interface {
Id() string Id() string
PublicId() string
} }
type Reply interface { type Reply interface {
@ -20,7 +21,8 @@ type BasicReply struct {
message string message string
} }
func NewBasicReply(source Identifier, code string, message string) *BasicReply { func NewBasicReply(source Identifier, code string, format string, args ...interface{}) *BasicReply {
message := fmt.Sprintf(format, args...)
fullMessage := fmt.Sprintf(":%s %s %s\r\n", source.Id(), code, message) fullMessage := fmt.Sprintf(":%s %s %s\r\n", source.Id(), code, message)
return &BasicReply{source, code, fullMessage} return &BasicReply{source, code, fullMessage}
} }
@ -33,8 +35,8 @@ type NumericReply struct {
*BasicReply *BasicReply
} }
func NewNumericReply(source Identifier, code string, message string) *NumericReply { func NewNumericReply(source Identifier, code string, format string, args ...interface{}) *NumericReply {
return &NumericReply{&BasicReply{source, code, message}} return &NumericReply{&BasicReply{source, code, fmt.Sprintf(format, args...)}}
} }
func (reply *NumericReply) String(client *Client) string { func (reply *NumericReply) String(client *Client) string {
@ -44,24 +46,24 @@ func (reply *NumericReply) String(client *Client) string {
// messaging replies // messaging replies
func RplPrivMsg(source *Client, target *Client, message string) Reply { func RplPrivMsg(source Identifier, target Identifier, message string) Reply {
return NewBasicReply(source, RPL_PRIVMSG, fmt.Sprintf("%s :%s", target, message)) return NewBasicReply(source, RPL_PRIVMSG, "%s :%s", target, message)
} }
func RplNick(client *Client, newNick string) Reply { func RplNick(client *Client, newNick string) Reply {
return NewBasicReply(client, RPL_NICK, newNick) return NewBasicReply(client, RPL_NICK, newNick)
} }
func RplPrivMsgChannel(channel *Channel, source *Client, message string) Reply { func RplPrivMsgChannel(channel *Channel, source Identifier, message string) Reply {
return NewBasicReply(source, RPL_PRIVMSG, fmt.Sprintf("%s :%s", channel.name, message)) return NewBasicReply(source, RPL_PRIVMSG, "%s :%s", channel.name, message)
} }
func RplJoin(channel *Channel, client *Client) Reply { func RplJoin(channel *Channel, user *User) Reply {
return NewBasicReply(client, RPL_JOIN, channel.name) return NewBasicReply(user, RPL_JOIN, channel.name)
} }
func RplPart(channel *Channel, client *Client, message string) Reply { func RplPart(channel *Channel, user *User, message string) Reply {
return NewBasicReply(client, RPL_PART, fmt.Sprintf("%s :%s", channel.name, message)) return NewBasicReply(user, RPL_PART, "%s :%s", channel.name, message)
} }
func RplPong(server *Server) Reply { func RplPong(server *Server) Reply {
@ -69,7 +71,7 @@ func RplPong(server *Server) Reply {
} }
func RplQuit(client *Client, message string) Reply { func RplQuit(client *Client, message string) Reply {
return NewBasicReply(client, RPL_QUIT, ":"+message) return NewBasicReply(client, RPL_QUIT, ":%", message)
} }
func RplInviteMsg(channel *Channel, inviter *Client) Reply { func RplInviteMsg(channel *Channel, inviter *Client) Reply {
@ -80,70 +82,75 @@ func RplInviteMsg(channel *Channel, inviter *Client) Reply {
func RplWelcome(source Identifier, client *Client) Reply { func RplWelcome(source Identifier, client *Client) Reply {
return NewNumericReply(source, RPL_WELCOME, return NewNumericReply(source, RPL_WELCOME,
"Welcome to the Internet Relay Network "+client.Id()) "Welcome to the Internet Relay Network %s", client.Id())
} }
func RplYourHost(server *Server, target *Client) Reply { func RplYourHost(server *Server, target *Client) Reply {
return NewNumericReply(server, RPL_YOURHOST, return NewNumericReply(server, RPL_YOURHOST,
fmt.Sprintf("Your host is %s, running version %s", server.hostname, VERSION)) "Your host is %s, running version %s", server.hostname, VERSION)
} }
func RplCreated(server *Server) Reply { func RplCreated(server *Server) Reply {
return NewNumericReply(server, RPL_CREATED, return NewNumericReply(server, RPL_CREATED,
"This server was created "+server.ctime.Format(time.RFC1123)) "This server was created %s", server.ctime.Format(time.RFC1123))
} }
func RplMyInfo(server *Server) Reply { func RplMyInfo(server *Server) Reply {
return NewNumericReply(server, RPL_MYINFO, return NewNumericReply(server, RPL_MYINFO,
fmt.Sprintf("%s %s w kn", server.name, VERSION)) "%s %s a kn", server.name, VERSION)
} }
func RplUModeIs(server *Server, client *Client) Reply { func RplUModeIs(server *Server, client *Client) Reply {
return NewNumericReply(server, RPL_UMODEIS, client.UModeString()) return NewNumericReply(server, RPL_UMODEIS,
client.UModeString())
} }
func RplNoTopic(channel *Channel) Reply { func RplNoTopic(channel *Channel) Reply {
return NewNumericReply(channel.server, RPL_NOTOPIC, channel.name+" :No topic is set") return NewNumericReply(channel.server, RPL_NOTOPIC,
"%s :No topic is set", channel.name)
} }
func RplTopic(channel *Channel) Reply { func RplTopic(channel *Channel) Reply {
return NewNumericReply(channel.server, RPL_TOPIC, return NewNumericReply(channel.server, RPL_TOPIC,
fmt.Sprintf("%s :%s", channel.name, channel.topic)) "%s :%s", channel.name, channel.topic)
} }
func RplInvitingMsg(channel *Channel, invitee *Client) Reply { func RplInvitingMsg(channel *Channel, invitee *Client) Reply {
return NewNumericReply(channel.server, RPL_INVITING, return NewNumericReply(channel.server, RPL_INVITING,
fmt.Sprintf("%s %s", channel.name, invitee.Nick())) "%s %s", channel.name, invitee.Nick())
} }
func RplNamReply(channel *Channel) Reply { func RplNamReply(channel *Channel) Reply {
// TODO multiple names and splitting based on message size // TODO multiple names and splitting based on message size
return NewNumericReply(channel.server, RPL_NAMREPLY, return NewNumericReply(channel.server, RPL_NAMREPLY,
fmt.Sprintf("= %s :%s", channel.name, strings.Join(channel.Nicks(), " "))) "= %s :%s", channel.name, strings.Join(channel.Nicks(), " "))
} }
func RplEndOfNames(source Identifier) Reply { func RplEndOfNames(source Identifier) Reply {
return NewNumericReply(source, RPL_ENDOFNAMES, ":End of NAMES list") return NewNumericReply(source, RPL_ENDOFNAMES,
":End of NAMES list")
} }
func RplYoureOper(server *Server) Reply { func RplYoureOper(server *Server) Reply {
return NewNumericReply(server, RPL_YOUREOPER, ":You are now an IRC operator") return NewNumericReply(server, RPL_YOUREOPER,
":You are now an IRC operator")
} }
// errors (also numeric) // errors (also numeric)
func ErrAlreadyRegistered(source Identifier) Reply { func ErrAlreadyRegistered(source Identifier) Reply {
return NewNumericReply(source, ERR_ALREADYREGISTRED, ":You may not reregister") return NewNumericReply(source, ERR_ALREADYREGISTRED,
":You may not reregister")
} }
func ErrNickNameInUse(source Identifier, nick string) Reply { func ErrNickNameInUse(source Identifier, nick string) Reply {
return NewNumericReply(source, ERR_NICKNAMEINUSE, return NewNumericReply(source, ERR_NICKNAMEINUSE,
nick+" :Nickname is already in use") "%s :Nickname is already in use", nick)
} }
func ErrUnknownCommand(source Identifier, command string) Reply { func ErrUnknownCommand(source Identifier, command string) Reply {
return NewNumericReply(source, ERR_UNKNOWNCOMMAND, return NewNumericReply(source, ERR_UNKNOWNCOMMAND,
command+" :Unknown command") "%s :Unknown command", command)
} }
func ErrUsersDontMatch(source Identifier) Reply { func ErrUsersDontMatch(source Identifier) Reply {
@ -153,37 +160,37 @@ func ErrUsersDontMatch(source Identifier) Reply {
func ErrNeedMoreParams(source Identifier, command string) Reply { func ErrNeedMoreParams(source Identifier, command string) Reply {
return NewNumericReply(source, ERR_NEEDMOREPARAMS, return NewNumericReply(source, ERR_NEEDMOREPARAMS,
command+"%s :Not enough parameters") "%s :Not enough parameters", command)
} }
func ErrNoSuchChannel(source Identifier, channel string) Reply { func ErrNoSuchChannel(source Identifier, channel string) Reply {
return NewNumericReply(source, ERR_NOSUCHCHANNEL, return NewNumericReply(source, ERR_NOSUCHCHANNEL,
channel+" :No such channel") "%s :No such channel", channel)
} }
func ErrUserOnChannel(channel *Channel, member *Client) Reply { func ErrUserOnChannel(channel *Channel, member *Client) Reply {
return NewNumericReply(channel.server, ERR_USERONCHANNEL, return NewNumericReply(channel.server, ERR_USERONCHANNEL,
fmt.Sprintf("%s %s :is already on channel", member.nick, channel.name)) "%s %s :is already on channel", member.nick, channel.name)
} }
func ErrNotOnChannel(channel *Channel) Reply { func ErrNotOnChannel(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_NOTONCHANNEL, return NewNumericReply(channel.server, ERR_NOTONCHANNEL,
channel.name+" :You're not on that channel") "%s :You're not on that channel", channel.name)
} }
func ErrInviteOnlyChannel(channel *Channel) Reply { func ErrInviteOnlyChannel(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_INVITEONLYCHAN, return NewNumericReply(channel.server, ERR_INVITEONLYCHAN,
channel.name+" :Cannot join channel (+i)") "%s :Cannot join channel (+i)", channel.name)
} }
func ErrBadChannelKey(channel *Channel) Reply { func ErrBadChannelKey(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_BADCHANNELKEY, return NewNumericReply(channel.server, ERR_BADCHANNELKEY,
channel.name+" :Cannot join channel (+k)") "%s :Cannot join channel (+k)", channel.name)
} }
func ErrNoSuchNick(source Identifier, nick string) Reply { func ErrNoSuchNick(source Identifier, nick string) Reply {
return NewNumericReply(source, ERR_NOSUCHNICK, return NewNumericReply(source, ERR_NOSUCHNICK,
nick+" :No such nick/channel") "%s :No such nick/channel", nick)
} }
func ErrPasswdMismatch(server *Server) Reply { func ErrPasswdMismatch(server *Server) Reply {
@ -192,5 +199,13 @@ func ErrPasswdMismatch(server *Server) Reply {
func ErrNoChanModes(channel *Channel) Reply { func ErrNoChanModes(channel *Channel) Reply {
return NewNumericReply(channel.server, ERR_NOCHANMODES, return NewNumericReply(channel.server, ERR_NOCHANMODES,
channel.name+" :Channel doesn't support modes") "%s :Channel doesn't support modes", channel.name)
}
func ErrNoPrivileges(server *Server) Reply {
return NewNumericReply(server, ERR_NOPRIVILEGES, ":Permission Denied")
}
func ErrRestricted(server *Server) Reply {
return NewNumericReply(server, ERR_RESTRICTED, ":Your connection is restricted!")
} }

View File

@ -1,39 +1,46 @@
package irc package irc
import ( import (
"code.google.com/p/go.crypto/bcrypt"
"log" "log"
"net" "net"
"time" "time"
) )
type ClientNameMap map[string]*Client
type ChannelNameMap map[string]*Channel
type UserNameMap map[string]*User
type Server struct { type Server struct {
hostname string hostname string
ctime time.Time ctime time.Time
name string name string
recv chan<- Message commands chan<- Command
password string password []byte
nicks map[string]*Client users UserNameMap
channels map[string]*Channel channels ChannelNameMap
} }
func NewServer(name string) *Server { func NewServer(name string) *Server {
recv := make(chan Message) commands := make(chan Command)
server := &Server{ server := &Server{
ctime: time.Now(), ctime: time.Now(),
name: name, name: name,
recv: recv, commands: commands,
nicks: make(map[string]*Client), users: make(UserNameMap),
channels: make(map[string]*Channel), channels: make(ChannelNameMap),
} }
go func() { go server.receiveCommands(commands)
for m := range recv {
m.Client().atime = time.Now()
m.Handle(server, m.Client())
}
}()
return server return server
} }
func (server *Server) receiveCommands(commands <-chan Command) {
for command := range commands {
command.Client().atime = time.Now()
command.Handle(server)
}
}
func (s *Server) Listen(addr string) { func (s *Server) Listen(addr string) {
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
@ -50,7 +57,7 @@ func (s *Server) Listen(addr string) {
continue continue
} }
log.Print("Server.Listen: accepted ", conn.RemoteAddr()) log.Print("Server.Listen: accepted ", conn.RemoteAddr())
go NewClient(s, conn) NewClient(s, conn)
} }
} }
@ -66,84 +73,220 @@ func (s *Server) GetOrMakeChannel(name string) *Channel {
} }
// Send a message to clients of channels fromClient is a member. // Send a message to clients of channels fromClient is a member.
func (s *Server) SendToInterestedClients(fromClient *Client, reply Reply) { func (s *Server) InterestedUsers(fromUser *User) UserSet {
users := make(UserSet)
clients := make(ClientSet) users[fromUser] = true
clients[fromClient] = true for channel := range fromUser.channels {
for channel := range fromClient.channels { for user := range channel.members {
for client := range channel.members { users[user] = true
clients[client] = true
} }
} }
for client := range clients { return users
client.send <- reply
}
} }
// server functionality // server functionality
func (s *Server) ChangeNick(c *Client, newNick string) {
if s.nicks[newNick] != nil {
c.send <- ErrNickNameInUse(s, newNick)
return
}
s.SendToInterestedClients(c, RplNick(c, newNick))
if c.nick != "" {
delete(s.nicks, c.nick)
}
s.nicks[newNick] = c
c.nick = newNick
s.tryRegister(c)
}
func (s *Server) UserLogin(c *Client, user string, realName string) {
if c.username != "" {
c.send <- ErrAlreadyRegistered(s)
return
}
c.username, c.realname = user, realName
s.tryRegister(c)
}
func (s *Server) tryRegister(c *Client) { func (s *Server) tryRegister(c *Client) {
if !c.registered && c.HasNick() && c.HasUser() && (s.password == "" || c.serverPass) { if !c.registered && c.HasNick() && c.HasUser() && (s.password == nil || c.serverPass) {
c.registered = true c.registered = true
c.send <- RplWelcome(s, c) replies := []Reply{RplWelcome(s, c), RplYourHost(s, c), RplCreated(s), RplMyInfo(s)}
c.send <- RplYourHost(s, c) for _, reply := range replies {
c.send <- RplCreated(s) c.replies <- reply
c.send <- RplMyInfo(s) }
} }
} }
func (s *Server) Quit(c *Client, message string) {
for channel := range c.channels {
channel.Part(c, message)
}
delete(s.nicks, c.nick)
s.SendToInterestedClients(c, RplQuit(c, message))
c.conn.Close()
}
func (s *Server) ChangeUserMode(c *Client, modes []string) { func (s *Server) ChangeUserMode(c *Client, modes []string) {
for _, mode := range modes { // Don't allow any mode changes.
switch mode { c.replies <- RplUModeIs(s, c)
case "+w":
c.wallOps = true
case "-w":
c.wallOps = false
}
}
c.send <- RplUModeIs(s, c)
} }
func (s *Server) Id() string { func (s *Server) Id() string {
return s.hostname return s.hostname
} }
func (s *Server) PublicId() string {
return s.Id()
}
func (s *Server) DeleteChannel(channel *Channel) { func (s *Server) DeleteChannel(channel *Channel) {
delete(s.channels, channel.name) delete(s.channels, channel.name)
} }
//
// commands
//
func (m *PingCommand) Handle(s *Server) {
m.Client().replies <- RplPong(s)
}
func (m *PongCommand) Handle(s *Server) {
// no-op
}
func (m *PassCommand) Handle(s *Server) {
err := bcrypt.CompareHashAndPassword(s.password, []byte(m.password))
if err != nil {
m.Client().replies <- ErrPasswdMismatch(s)
return
}
m.Client().serverPass = true
// no reply?
}
func (m *NickCommand) Handle(s *Server) {
c := m.Client()
if c.user == nil {
c.replies <- RplNick(c, m.nickname)
c.nick = m.nickname
s.tryRegister(c)
return
}
c.user.replies <- ErrNoPrivileges(s)
}
func (m *UserCommand) Handle(s *Server) {
c := m.Client()
if c.username != "" {
c.replies <- ErrAlreadyRegistered(s)
return
}
c.username, c.realname = m.user, m.realname
s.tryRegister(c)
}
func (m *QuitCommand) Handle(s *Server) {
c := m.Client()
reply := RplQuit(c, m.message)
for user := range s.InterestedUsers(c.user) {
user.replies <- reply
}
c.conn.Close()
user := c.user
user.LogoutClient(c)
if !user.HasClients() {
cmd := &PartChannelCommand{
Command: m,
}
for channel := range c.user.channels {
channel.commands <- cmd
}
}
}
func (m *JoinCommand) Handle(s *Server) {
c := m.Client()
if m.zero {
cmd := &PartChannelCommand{
Command: m,
}
for channel := range c.user.channels {
channel.commands <- cmd
}
} else {
for i, name := range m.channels {
key := ""
if len(m.keys) > i {
key = m.keys[i]
}
s.GetOrMakeChannel(name).commands <- &JoinChannelCommand{m, key}
}
}
}
func (m *PartCommand) Handle(s *Server) {
user := m.Client().user
for _, chname := range m.channels {
channel := s.channels[chname]
if channel == nil {
user.replies <- ErrNoSuchChannel(s, channel.name)
continue
}
channel.commands <- &PartChannelCommand{m, m.message}
}
}
func (m *TopicCommand) Handle(s *Server) {
user := m.Client().user
channel := s.channels[m.channel]
if channel == nil {
user.replies <- ErrNoSuchChannel(s, m.channel)
return
}
if m.topic == "" {
channel.commands <- &GetTopicChannelCommand{m}
return
}
channel.commands <- &SetTopicChannelCommand{m}
}
func (m *PrivMsgCommand) Handle(s *Server) {
user := m.Client().user
if m.TargetIsChannel() {
channel := s.channels[m.target]
if channel == nil {
user.replies <- ErrNoSuchNick(s, m.target)
return
}
channel.commands <- &PrivMsgChannelCommand{m}
return
}
target := s.users[m.target]
if target != nil {
target.replies <- ErrNoSuchNick(s, m.target)
return
}
target.replies <- RplPrivMsg(user, target, m.message)
}
func (m *LoginCommand) Handle(s *Server) {
client := m.Client()
if client.user != nil {
client.replies <- ErrAlreadyRegistered(s)
return
}
user := s.users[m.nick]
if user == nil {
client.replies <- ErrNoSuchNick(s, m.nick)
return
}
if !user.Login(client, m.nick, m.password) {
client.replies <- ErrRestricted(s)
return
}
client.replies <- RplNick(client, m.nick)
// TODO join channels
}
func (m *ReserveCommand) Handle(s *Server) {
client := m.Client()
if client.user != nil {
client.replies <- ErrAlreadyRegistered(s)
return
}
if s.users[m.nick] != nil {
client.replies <- ErrNickNameInUse(s, m.nick)
return
}
s.users[m.nick] = NewUser(m.nick, m.password, s)
}

View File

@ -1,5 +1,100 @@
package irc package irc
import (
"code.google.com/p/go.crypto/bcrypt"
"fmt"
)
type User struct { type User struct {
nick string nick string
hash []byte
server *Server
replies chan<- Reply
commands <-chan Command
clients ClientSet
channels ChannelSet
}
type UserSet map[*User]bool
func (set UserSet) Add(user *User) {
set[user] = true
}
func (set UserSet) Remove(user *User) {
delete(set, user)
}
func NewUser(nick string, password string, server *Server) *User {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
panic("bcrypt failed; cannot generate password hash")
}
replies := make(chan Reply)
user := &User{
nick: nick,
hash: hash,
server: server,
clients: make(ClientSet),
replies: replies,
}
go user.receiveReplies(replies)
return user
}
// Distribute replies to clients.
func (user *User) receiveReplies(replies <-chan Reply) {
for reply := range replies {
for client := range user.clients {
client.replies <- reply
}
}
}
// Identifier
func (user *User) Id() string {
return fmt.Sprintf("%s!%s@%s", user.nick, user.nick, user.server.Id())
}
func (user *User) PublicId() string {
return user.Id()
}
func (user *User) Nick() string {
return user.nick
}
func (user *User) Login(c *Client, nick string, password string) bool {
if nick != c.nick {
return false
}
if user.hash == nil {
return false
}
err := bcrypt.CompareHashAndPassword(user.hash, []byte(password))
if err != nil {
c.replies <- ErrNoPrivileges(user.server)
return false
}
user.clients[c] = true
c.user = user
c.replies <- RplNick(c, user.nick)
// TODO join channels
return true
}
func (user *User) LogoutClient(c *Client) bool {
if user.clients[c] {
delete(user.clients, c)
return true
}
return false
}
func (user *User) HasClients() bool {
return len(user.clients) > 0
} }