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:
parent
559445d9a8
commit
f2aedbaffd
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
|||||||
pkg
|
pkg
|
||||||
bin
|
bin
|
||||||
ergonomadic
|
src/code.google.com/
|
||||||
|
4
build.sh
4
build.sh
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
18
src/genpasswd/genpasswd.go
Normal file
18
src/genpasswd/genpasswd.go
Normal 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))
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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!")
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user