Code refactoring
* Less memory allocations * Daemon instance replaced with global variables * Code simplification * Asynchronously send messages to clients
This commit is contained in:
parent
186ec4b4bd
commit
e657ffd2ab
121
client.go
121
client.go
@ -23,11 +23,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BufSize = 1500
|
BufSize = 1500
|
||||||
|
MaxOutBuf = 1 << 12
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -35,35 +37,60 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
hostname *string
|
conn net.Conn
|
||||||
conn net.Conn
|
registered bool
|
||||||
registered bool
|
nickname *string
|
||||||
nickname string
|
username *string
|
||||||
username string
|
realname *string
|
||||||
realname string
|
password *string
|
||||||
password string
|
away *string
|
||||||
away *string
|
recvTimestamp time.Time
|
||||||
|
sendTimestamp time.Time
|
||||||
|
outBuf chan string
|
||||||
|
alive bool
|
||||||
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientAlivenessState struct {
|
func (c Client) String() string {
|
||||||
pingSent bool
|
return *c.nickname + "!" + *c.username + "@" + c.conn.RemoteAddr().String()
|
||||||
timestamp time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client Client) String() string {
|
func NewClient(conn net.Conn) *Client {
|
||||||
return client.nickname + "!" + client.username + "@" + client.conn.RemoteAddr().String()
|
nickname := "*"
|
||||||
|
username := ""
|
||||||
|
c := Client{
|
||||||
|
conn: conn,
|
||||||
|
nickname: &nickname,
|
||||||
|
username: &username,
|
||||||
|
recvTimestamp: time.Now(),
|
||||||
|
sendTimestamp: time.Now(),
|
||||||
|
alive: true,
|
||||||
|
outBuf: make(chan string, MaxOutBuf),
|
||||||
|
}
|
||||||
|
go c.MsgSender()
|
||||||
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(hostname *string, conn net.Conn) *Client {
|
func (c *Client) SetDead() {
|
||||||
return &Client{hostname: hostname, conn: conn, nickname: "*", password: ""}
|
close(c.outBuf)
|
||||||
|
c.alive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() {
|
||||||
|
c.Lock()
|
||||||
|
c.conn.Close()
|
||||||
|
if c.alive {
|
||||||
|
c.SetDead()
|
||||||
|
}
|
||||||
|
c.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client processor blockingly reads everything remote client sends,
|
// Client processor blockingly reads everything remote client sends,
|
||||||
// splits messages by CRLF and send them to Daemon gorouting for processing
|
// splits messages by CRLF and send them to Daemon gorouting for processing
|
||||||
// it futher. Also it can signalize that client is unavailable (disconnected).
|
// it futher. Also it can signalize that client is unavailable (disconnected).
|
||||||
func (client *Client) Processor(sink chan<- ClientEvent) {
|
func (c *Client) Processor(sink chan ClientEvent) {
|
||||||
sink <- ClientEvent{client, EventNew, ""}
|
sink <- ClientEvent{c, EventNew, ""}
|
||||||
log.Println(client, "New client")
|
log.Println(c, "New client")
|
||||||
buf := make([]byte, BufSize*2)
|
buf := make([]byte, BufSize*2)
|
||||||
var n int
|
var n int
|
||||||
var prev int
|
var prev int
|
||||||
@ -71,14 +98,11 @@ func (client *Client) Processor(sink chan<- ClientEvent) {
|
|||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
if prev == BufSize {
|
if prev == BufSize {
|
||||||
log.Println(client, "buffer size exceeded, kicking him")
|
log.Println(c, "input buffer size exceeded, kicking him")
|
||||||
sink <- ClientEvent{client, EventDel, ""}
|
|
||||||
client.conn.Close()
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
n, err = client.conn.Read(buf[prev:])
|
n, err = c.conn.Read(buf[prev:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sink <- ClientEvent{client, EventDel, ""}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
prev += n
|
prev += n
|
||||||
@ -87,50 +111,69 @@ func (client *Client) Processor(sink chan<- ClientEvent) {
|
|||||||
if i == -1 {
|
if i == -1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sink <- ClientEvent{client, EventMsg, string(buf[:i])}
|
sink <- ClientEvent{c, EventMsg, string(buf[:i])}
|
||||||
copy(buf, buf[i+2:prev])
|
copy(buf, buf[i+2:prev])
|
||||||
prev -= (i + 2)
|
prev -= (i + 2)
|
||||||
goto CheckMore
|
goto CheckMore
|
||||||
}
|
}
|
||||||
|
c.Close()
|
||||||
|
sink <- ClientEvent{c, EventDel, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MsgSender() {
|
||||||
|
for msg := range c.outBuf {
|
||||||
|
c.conn.Write(append([]byte(msg), CRLF...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message as is with CRLF appended.
|
// Send message as is with CRLF appended.
|
||||||
func (client *Client) Msg(text string) {
|
func (c *Client) Msg(text string) {
|
||||||
client.conn.Write(append([]byte(text), CRLF...))
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
if !c.alive {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(c.outBuf) == MaxOutBuf {
|
||||||
|
log.Println(c, "output buffer size exceeded, kicking him")
|
||||||
|
go c.Close()
|
||||||
|
c.SetDead()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.outBuf <- text
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message from server. It has ": servername" prefix.
|
// Send message from server. It has ": servername" prefix.
|
||||||
func (client *Client) Reply(text string) {
|
func (c *Client) Reply(text string) {
|
||||||
client.Msg(":" + *client.hostname + " " + text)
|
c.Msg(":" + *hostname + " " + text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send server message, concatenating all provided text parts and
|
// Send server message, concatenating all provided text parts and
|
||||||
// prefix the last one with ":".
|
// prefix the last one with ":".
|
||||||
func (client *Client) ReplyParts(code string, text ...string) {
|
func (c *Client) ReplyParts(code string, text ...string) {
|
||||||
parts := []string{code}
|
parts := []string{code}
|
||||||
for _, t := range text {
|
for _, t := range text {
|
||||||
parts = append(parts, t)
|
parts = append(parts, t)
|
||||||
}
|
}
|
||||||
parts[len(parts)-1] = ":" + parts[len(parts)-1]
|
parts[len(parts)-1] = ":" + parts[len(parts)-1]
|
||||||
client.Reply(strings.Join(parts, " "))
|
c.Reply(strings.Join(parts, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send nicknamed server message. After servername it always has target
|
// Send nicknamed server message. After servername it always has target
|
||||||
// client's nickname. The last part is prefixed with ":".
|
// client's nickname. The last part is prefixed with ":".
|
||||||
func (client *Client) ReplyNicknamed(code string, text ...string) {
|
func (c *Client) ReplyNicknamed(code string, text ...string) {
|
||||||
client.ReplyParts(code, append([]string{client.nickname}, text...)...)
|
c.ReplyParts(code, append([]string{*c.nickname}, text...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply "461 not enough parameters" error for given command.
|
// Reply "461 not enough parameters" error for given command.
|
||||||
func (client *Client) ReplyNotEnoughParameters(command string) {
|
func (c *Client) ReplyNotEnoughParameters(command string) {
|
||||||
client.ReplyNicknamed("461", command, "Not enough parameters")
|
c.ReplyNicknamed("461", command, "Not enough parameters")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply "403 no such channel" error for specified channel.
|
// Reply "403 no such channel" error for specified channel.
|
||||||
func (client *Client) ReplyNoChannel(channel string) {
|
func (c *Client) ReplyNoChannel(channel string) {
|
||||||
client.ReplyNicknamed("403", channel, "No such channel")
|
c.ReplyNicknamed("403", channel, "No such channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) ReplyNoNickChan(channel string) {
|
func (c *Client) ReplyNoNickChan(channel string) {
|
||||||
client.ReplyNicknamed("401", channel, "No such nick/channel")
|
c.ReplyNicknamed("401", channel, "No such nick/channel")
|
||||||
}
|
}
|
||||||
|
@ -19,86 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Testing network connection that satisfies net.Conn interface
|
|
||||||
// Can send predefined messages and store all written ones
|
|
||||||
type TestingConn struct {
|
|
||||||
inbound chan string
|
|
||||||
outbound chan string
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestingConn() *TestingConn {
|
|
||||||
inbound := make(chan string, 8)
|
|
||||||
outbound := make(chan string, 8)
|
|
||||||
return &TestingConn{inbound: inbound, outbound: outbound}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn TestingConn) Error() string {
|
|
||||||
return "i am finished"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *TestingConn) Read(b []byte) (n int, err error) {
|
|
||||||
msg := <-conn.inbound
|
|
||||||
if msg == "" {
|
|
||||||
return 0, conn
|
|
||||||
}
|
|
||||||
for n, bt := range append([]byte(msg), CRLF...) {
|
|
||||||
b[n] = bt
|
|
||||||
}
|
|
||||||
return len(msg)+2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyAddr struct{}
|
|
||||||
|
|
||||||
func (a MyAddr) String() string {
|
|
||||||
return "someclient"
|
|
||||||
}
|
|
||||||
func (a MyAddr) Network() string {
|
|
||||||
return "somenet"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *TestingConn) Write(b []byte) (n int, err error) {
|
|
||||||
conn.outbound <- string(b)
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *TestingConn) Close() error {
|
|
||||||
conn.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn TestingConn) LocalAddr() net.Addr {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn TestingConn) RemoteAddr() net.Addr {
|
|
||||||
return MyAddr{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn TestingConn) SetDeadline(t time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn TestingConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn TestingConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New client creation test. It must send an event about new client,
|
// New client creation test. It must send an event about new client,
|
||||||
// two predefined messages from it and deletion one
|
// two predefined messages from it and deletion one
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
conn := NewTestingConn()
|
conn := NewTestingConn()
|
||||||
sink := make(chan ClientEvent)
|
sink := make(chan ClientEvent)
|
||||||
host := "foohost"
|
host := "foohost"
|
||||||
client := NewClient(&host, conn)
|
hostname = &host
|
||||||
|
client := NewClient(conn)
|
||||||
go client.Processor(sink)
|
go client.Processor(sink)
|
||||||
|
|
||||||
event := <-sink
|
event := <-sink
|
||||||
@ -126,8 +57,10 @@ func TestNewClient(t *testing.T) {
|
|||||||
func TestClientReplies(t *testing.T) {
|
func TestClientReplies(t *testing.T) {
|
||||||
conn := NewTestingConn()
|
conn := NewTestingConn()
|
||||||
host := "foohost"
|
host := "foohost"
|
||||||
client := NewClient(&host, conn)
|
hostname = &host
|
||||||
client.nickname = "мойник"
|
client := NewClient(conn)
|
||||||
|
nickname := "мойник"
|
||||||
|
client.nickname = &nickname
|
||||||
|
|
||||||
client.Reply("hello")
|
client.Reply("hello")
|
||||||
if r := <-conn.outbound; r != ":foohost hello\r\n" {
|
if r := <-conn.outbound; r != ":foohost hello\r\n" {
|
||||||
|
93
common_test.go
Normal file
93
common_test.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
goircd -- minimalistic simple Internet Relay Chat (IRC) server
|
||||||
|
Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Testing network connection that satisfies net.Conn interface
|
||||||
|
// Can send predefined messages and store all written ones
|
||||||
|
type TestingConn struct {
|
||||||
|
inbound chan string
|
||||||
|
outbound chan string
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestingConn() *TestingConn {
|
||||||
|
inbound := make(chan string, 8)
|
||||||
|
outbound := make(chan string, 8)
|
||||||
|
return &TestingConn{inbound: inbound, outbound: outbound}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn TestingConn) Error() string {
|
||||||
|
return "i am finished"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *TestingConn) Read(b []byte) (n int, err error) {
|
||||||
|
msg := <-conn.inbound
|
||||||
|
if msg == "" {
|
||||||
|
return 0, conn
|
||||||
|
}
|
||||||
|
for n, bt := range append([]byte(msg), CRLF...) {
|
||||||
|
b[n] = bt
|
||||||
|
}
|
||||||
|
return len(msg) + 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyAddr struct{}
|
||||||
|
|
||||||
|
func (a MyAddr) String() string {
|
||||||
|
return "someclient"
|
||||||
|
}
|
||||||
|
func (a MyAddr) Network() string {
|
||||||
|
return "somenet"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *TestingConn) Write(b []byte) (n int, err error) {
|
||||||
|
conn.outbound <- string(b)
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *TestingConn) Close() error {
|
||||||
|
conn.closed = true
|
||||||
|
close(conn.outbound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn TestingConn) LocalAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn TestingConn) RemoteAddr() net.Addr {
|
||||||
|
return MyAddr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn TestingConn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn TestingConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn TestingConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
391
daemon.go
391
daemon.go
@ -26,56 +26,28 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Max time deadline for client's unresponsiveness
|
// Max deadline time of client's unresponsiveness
|
||||||
PingTimeout = time.Second * 180
|
PingTimeout = time.Second * 180
|
||||||
// Max idle client's time before PING are sent
|
// Max idle client's time before PING are sent
|
||||||
PingThreshold = time.Second * 90
|
PingThreshold = time.Second * 90
|
||||||
// Client's aliveness check period
|
|
||||||
AlivenessCheck = time.Second * 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$")
|
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$")
|
||||||
|
|
||||||
|
roomsGroup sync.WaitGroup
|
||||||
|
|
||||||
|
clients map[*Client]struct{} = make(map[*Client]struct{})
|
||||||
)
|
)
|
||||||
|
|
||||||
type Daemon struct {
|
func SendLusers(client *Client) {
|
||||||
Verbose bool
|
|
||||||
version string
|
|
||||||
hostname *string
|
|
||||||
motd *string
|
|
||||||
passwords *string
|
|
||||||
clients map[*Client]struct{}
|
|
||||||
clientAliveness map[*Client]*ClientAlivenessState
|
|
||||||
rooms map[string]*Room
|
|
||||||
roomSinks map[*Room]chan ClientEvent
|
|
||||||
lastAlivenessCheck time.Time
|
|
||||||
logSink chan<- LogEvent
|
|
||||||
stateSink chan<- StateEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDaemon(version string, hostname, motd, passwords *string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Daemon {
|
|
||||||
daemon := Daemon{
|
|
||||||
version: version,
|
|
||||||
hostname: hostname,
|
|
||||||
motd: motd,
|
|
||||||
passwords: passwords,
|
|
||||||
}
|
|
||||||
daemon.clients = make(map[*Client]struct{})
|
|
||||||
daemon.clientAliveness = make(map[*Client]*ClientAlivenessState)
|
|
||||||
daemon.rooms = make(map[string]*Room)
|
|
||||||
daemon.roomSinks = make(map[*Room]chan ClientEvent)
|
|
||||||
daemon.logSink = logSink
|
|
||||||
daemon.stateSink = stateSink
|
|
||||||
return &daemon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (daemon *Daemon) SendLusers(client *Client) {
|
|
||||||
lusers := 0
|
lusers := 0
|
||||||
for client := range daemon.clients {
|
for client := range clients {
|
||||||
if client.registered {
|
if client.registered {
|
||||||
lusers++
|
lusers++
|
||||||
}
|
}
|
||||||
@ -83,79 +55,87 @@ func (daemon *Daemon) SendLusers(client *Client) {
|
|||||||
client.ReplyNicknamed("251", fmt.Sprintf("There are %d users and 0 invisible on 1 servers", lusers))
|
client.ReplyNicknamed("251", fmt.Sprintf("There are %d users and 0 invisible on 1 servers", lusers))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) SendMotd(client *Client) {
|
func SendMotd(client *Client) {
|
||||||
if daemon.motd == nil || *daemon.motd == "" {
|
if motd == nil {
|
||||||
client.ReplyNicknamed("422", "MOTD File is missing")
|
client.ReplyNicknamed("422", "MOTD File is missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
motdText, err := ioutil.ReadFile(*motd)
|
||||||
motd, err := ioutil.ReadFile(*daemon.motd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Can not read motd file %s: %v", *daemon.motd, err)
|
log.Printf("Can not read motd file %s: %v", *motd, err)
|
||||||
client.ReplyNicknamed("422", "Error reading MOTD File")
|
client.ReplyNicknamed("422", "Error reading MOTD File")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
client.ReplyNicknamed("375", "- "+*hostname+" Message of the day -")
|
||||||
client.ReplyNicknamed("375", "- "+*daemon.hostname+" Message of the day -")
|
for _, s := range strings.Split(strings.Trim(string(motdText), "\n"), "\n") {
|
||||||
for _, s := range strings.Split(strings.Trim(string(motd), "\n"), "\n") {
|
client.ReplyNicknamed("372", "- "+s)
|
||||||
client.ReplyNicknamed("372", "- "+string(s))
|
|
||||||
}
|
}
|
||||||
client.ReplyNicknamed("376", "End of /MOTD command")
|
client.ReplyNicknamed("376", "End of /MOTD command")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) SendWhois(client *Client, nicknames []string) {
|
func SendWhois(client *Client, nicknames []string) {
|
||||||
|
var c *Client
|
||||||
|
var hostPort string
|
||||||
|
var err error
|
||||||
|
var subscriptions []string
|
||||||
|
var room *Room
|
||||||
|
var subscriber *Client
|
||||||
for _, nickname := range nicknames {
|
for _, nickname := range nicknames {
|
||||||
nickname = strings.ToLower(nickname)
|
nickname = strings.ToLower(nickname)
|
||||||
found := false
|
for c = range clients {
|
||||||
for c := range daemon.clients {
|
if strings.ToLower(*c.nickname) == nickname {
|
||||||
if strings.ToLower(c.nickname) != nickname {
|
goto Found
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
found = true
|
}
|
||||||
h := c.conn.RemoteAddr().String()
|
client.ReplyNoNickChan(nickname)
|
||||||
h, _, err := net.SplitHostPort(h)
|
continue
|
||||||
if err != nil {
|
Found:
|
||||||
log.Printf("Can't parse RemoteAddr %q: %v", h, err)
|
hostPort, _, err = net.SplitHostPort(c.conn.RemoteAddr().String())
|
||||||
h = "Unknown"
|
if err != nil {
|
||||||
}
|
log.Printf("Can't parse RemoteAddr %q: %v", hostPort, err)
|
||||||
client.ReplyNicknamed("311", c.nickname, c.username, h, "*", c.realname)
|
hostPort = "Unknown"
|
||||||
client.ReplyNicknamed("312", c.nickname, *daemon.hostname, *daemon.hostname)
|
}
|
||||||
if c.away != nil {
|
client.ReplyNicknamed("311", *c.nickname, *c.username, hostPort, "*", *c.realname)
|
||||||
client.ReplyNicknamed("301", c.nickname, *c.away)
|
client.ReplyNicknamed("312", *c.nickname, *hostname, *hostname)
|
||||||
}
|
if c.away != nil {
|
||||||
subscriptions := []string{}
|
client.ReplyNicknamed("301", *c.nickname, *c.away)
|
||||||
for _, room := range daemon.rooms {
|
}
|
||||||
for subscriber := range room.members {
|
subscriptions = make([]string, 0)
|
||||||
if subscriber.nickname == nickname {
|
for _, room = range rooms {
|
||||||
subscriptions = append(subscriptions, room.name)
|
for subscriber = range room.members {
|
||||||
}
|
if *subscriber.nickname == nickname {
|
||||||
|
subscriptions = append(subscriptions, *room.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(subscriptions)
|
|
||||||
client.ReplyNicknamed("319", c.nickname, strings.Join(subscriptions, " "))
|
|
||||||
client.ReplyNicknamed("318", c.nickname, "End of /WHOIS list")
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
client.ReplyNoNickChan(nickname)
|
|
||||||
}
|
}
|
||||||
|
sort.Strings(subscriptions)
|
||||||
|
client.ReplyNicknamed("319", *c.nickname, strings.Join(subscriptions, " "))
|
||||||
|
client.ReplyNicknamed("318", *c.nickname, "End of /WHOIS list")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) SendList(client *Client, cols []string) {
|
func SendList(client *Client, cols []string) {
|
||||||
var rooms []string
|
var rs []string
|
||||||
|
var r string
|
||||||
if (len(cols) > 1) && (cols[1] != "") {
|
if (len(cols) > 1) && (cols[1] != "") {
|
||||||
rooms = strings.Split(strings.Split(cols[1], " ")[0], ",")
|
rs = strings.Split(strings.Split(cols[1], " ")[0], ",")
|
||||||
} else {
|
} else {
|
||||||
rooms = []string{}
|
rs = make([]string, 0)
|
||||||
for room := range daemon.rooms {
|
for r = range rooms {
|
||||||
rooms = append(rooms, room)
|
rs = append(rs, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(rooms)
|
sort.Strings(rs)
|
||||||
for _, room := range rooms {
|
var room *Room
|
||||||
r, found := daemon.rooms[room]
|
var found bool
|
||||||
if found {
|
for _, r = range rs {
|
||||||
client.ReplyNicknamed("322", room, fmt.Sprintf("%d", len(r.members)), r.topic)
|
if room, found = rooms[r]; found {
|
||||||
|
client.ReplyNicknamed(
|
||||||
|
"322",
|
||||||
|
r,
|
||||||
|
fmt.Sprintf("%d", len(room.members)),
|
||||||
|
*room.topic,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client.ReplyNicknamed("323", "End of /LIST")
|
client.ReplyNicknamed("323", "End of /LIST")
|
||||||
@ -166,14 +146,14 @@ func (daemon *Daemon) SendList(client *Client, cols []string) {
|
|||||||
// * only QUIT, NICK and USER commands are processed
|
// * only QUIT, NICK and USER commands are processed
|
||||||
// * other commands are quietly ignored
|
// * other commands are quietly ignored
|
||||||
// When client finishes NICK/USER workflow, then MOTD and LUSERS are send to him.
|
// When client finishes NICK/USER workflow, then MOTD and LUSERS are send to him.
|
||||||
func (daemon *Daemon) ClientRegister(client *Client, command string, cols []string) {
|
func ClientRegister(client *Client, cmd string, cols []string) {
|
||||||
switch command {
|
switch cmd {
|
||||||
case "PASS":
|
case "PASS":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("PASS")
|
client.ReplyNotEnoughParameters("PASS")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.password = cols[1]
|
client.password = &cols[1]
|
||||||
case "NICK":
|
case "NICK":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyParts("431", "No nickname given")
|
client.ReplyParts("431", "No nickname given")
|
||||||
@ -182,8 +162,8 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
|
|||||||
nickname := cols[1]
|
nickname := cols[1]
|
||||||
// Compatibility with some clients prepending colons to nickname
|
// Compatibility with some clients prepending colons to nickname
|
||||||
nickname = strings.TrimPrefix(nickname, ":")
|
nickname = strings.TrimPrefix(nickname, ":")
|
||||||
for existingClient := range daemon.clients {
|
for existingClient := range clients {
|
||||||
if existingClient.nickname == nickname {
|
if *existingClient.nickname == nickname {
|
||||||
client.ReplyParts("433", "*", nickname, "Nickname is already in use")
|
client.ReplyParts("433", "*", nickname, "Nickname is already in use")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -192,7 +172,7 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
|
|||||||
client.ReplyParts("432", "*", cols[1], "Erroneous nickname")
|
client.ReplyParts("432", "*", cols[1], "Erroneous nickname")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.nickname = nickname
|
client.nickname = &nickname
|
||||||
case "USER":
|
case "USER":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.ReplyNotEnoughParameters("USER")
|
client.ReplyNotEnoughParameters("USER")
|
||||||
@ -203,66 +183,69 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
|
|||||||
client.ReplyNotEnoughParameters("USER")
|
client.ReplyNotEnoughParameters("USER")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.username = args[0]
|
client.username = &args[0]
|
||||||
client.realname = strings.TrimLeft(args[3], ":")
|
realname := strings.TrimLeft(args[3], ":")
|
||||||
|
client.realname = &realname
|
||||||
}
|
}
|
||||||
if client.nickname != "*" && client.username != "" {
|
if *client.nickname != "*" && *client.username != "" {
|
||||||
if daemon.passwords != nil && *daemon.passwords != "" {
|
if passwords != nil && *passwords != "" {
|
||||||
if client.password == "" {
|
if client.password == nil {
|
||||||
client.ReplyParts("462", "You may not register")
|
client.ReplyParts("462", "You may not register")
|
||||||
client.conn.Close()
|
client.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
contents, err := ioutil.ReadFile(*daemon.passwords)
|
contents, err := ioutil.ReadFile(*passwords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Can no read passwords file %s: %s", *daemon.passwords, err)
|
log.Fatalf("Can no read passwords file %s: %s", *passwords, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, entry := range strings.Split(string(contents), "\n") {
|
for _, entry := range strings.Split(string(contents), "\n") {
|
||||||
if entry == "" {
|
if entry == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if lp := strings.Split(entry, ":"); lp[0] == client.nickname && lp[1] != client.password {
|
if lp := strings.Split(entry, ":"); lp[0] == *client.nickname && lp[1] != *client.password {
|
||||||
client.ReplyParts("462", "You may not register")
|
client.ReplyParts("462", "You may not register")
|
||||||
client.conn.Close()
|
client.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.registered = true
|
client.registered = true
|
||||||
client.ReplyNicknamed("001", "Hi, welcome to IRC")
|
client.ReplyNicknamed("001", "Hi, welcome to IRC")
|
||||||
client.ReplyNicknamed("002", "Your host is "+*daemon.hostname+", running goircd "+daemon.version)
|
client.ReplyNicknamed("002", "Your host is "+*hostname+", running goircd "+version)
|
||||||
client.ReplyNicknamed("003", "This server was created sometime")
|
client.ReplyNicknamed("003", "This server was created sometime")
|
||||||
client.ReplyNicknamed("004", *daemon.hostname+" goircd o o")
|
client.ReplyNicknamed("004", *hostname+" goircd o o")
|
||||||
daemon.SendLusers(client)
|
SendLusers(client)
|
||||||
daemon.SendMotd(client)
|
SendMotd(client)
|
||||||
log.Println(client, "logged in")
|
log.Println(client, "logged in")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register new room in Daemon. Create an object, events sink, save pointers
|
// Register new room in Daemon. Create an object, events sink, save pointers
|
||||||
// to corresponding daemon's places and start room's processor goroutine.
|
// to corresponding daemon's places and start room's processor goroutine.
|
||||||
func (daemon *Daemon) RoomRegister(name string) (*Room, chan<- ClientEvent) {
|
func RoomRegister(name string) (*Room, chan ClientEvent) {
|
||||||
roomNew := NewRoom(daemon.hostname, name, daemon.logSink, daemon.stateSink)
|
roomNew := NewRoom(name)
|
||||||
roomNew.Verbose = daemon.Verbose
|
|
||||||
roomSink := make(chan ClientEvent)
|
roomSink := make(chan ClientEvent)
|
||||||
daemon.rooms[name] = roomNew
|
rooms[name] = roomNew
|
||||||
daemon.roomSinks[roomNew] = roomSink
|
roomSinks[roomNew] = roomSink
|
||||||
go roomNew.Processor(roomSink)
|
go roomNew.Processor(roomSink)
|
||||||
|
roomsGroup.Add(1)
|
||||||
return roomNew, roomSink
|
return roomNew, roomSink
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) HandlerJoin(client *Client, cmd string) {
|
func HandlerJoin(client *Client, cmd string) {
|
||||||
args := strings.Split(cmd, " ")
|
args := strings.Split(cmd, " ")
|
||||||
rooms := strings.Split(args[0], ",")
|
rs := strings.Split(args[0], ",")
|
||||||
var keys []string
|
var keys []string
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
keys = strings.Split(args[1], ",")
|
keys = strings.Split(args[1], ",")
|
||||||
} else {
|
} else {
|
||||||
keys = []string{}
|
keys = make([]string, 0)
|
||||||
}
|
}
|
||||||
for n, room := range rooms {
|
var roomExisting *Room
|
||||||
|
var roomSink chan ClientEvent
|
||||||
|
var roomNew *Room
|
||||||
|
for n, room := range rs {
|
||||||
if !RoomNameValid(room) {
|
if !RoomNameValid(room) {
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
continue
|
continue
|
||||||
@ -273,97 +256,88 @@ func (daemon *Daemon) HandlerJoin(client *Client, cmd string) {
|
|||||||
} else {
|
} else {
|
||||||
key = ""
|
key = ""
|
||||||
}
|
}
|
||||||
denied := false
|
for roomExisting, roomSink = range roomSinks {
|
||||||
joined := false
|
if room == *roomExisting.name {
|
||||||
for roomExisting, roomSink := range daemon.roomSinks {
|
if (roomExisting.key != nil) && (*roomExisting.key != key) {
|
||||||
if room == roomExisting.name {
|
goto Denied
|
||||||
if (roomExisting.key != "") && (roomExisting.key != key) {
|
|
||||||
denied = true
|
|
||||||
} else {
|
|
||||||
roomSink <- ClientEvent{client, EventNew, ""}
|
|
||||||
joined = true
|
|
||||||
}
|
}
|
||||||
break
|
roomSink <- ClientEvent{client, EventNew, ""}
|
||||||
|
goto Joined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if denied {
|
roomNew, roomSink = RoomRegister(room)
|
||||||
client.ReplyNicknamed("475", room, "Cannot join channel (+k) - bad key")
|
|
||||||
}
|
|
||||||
if denied || joined {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
roomNew, roomSink := daemon.RoomRegister(room)
|
|
||||||
log.Println("Room", roomNew, "created")
|
log.Println("Room", roomNew, "created")
|
||||||
if key != "" {
|
if key != "" {
|
||||||
roomNew.key = key
|
roomNew.key = &key
|
||||||
roomNew.StateSave()
|
roomNew.StateSave()
|
||||||
}
|
}
|
||||||
roomSink <- ClientEvent{client, EventNew, ""}
|
roomSink <- ClientEvent{client, EventNew, ""}
|
||||||
|
continue
|
||||||
|
Denied:
|
||||||
|
client.ReplyNicknamed("475", room, "Cannot join channel (+k) - bad key")
|
||||||
|
Joined:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
func Processor(events chan ClientEvent, finished chan struct{}) {
|
||||||
var now time.Time
|
var now time.Time
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
events <- ClientEvent{eventType: EventTick}
|
||||||
|
}
|
||||||
|
}()
|
||||||
for event := range events {
|
for event := range events {
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
client := event.client
|
client := event.client
|
||||||
|
switch event.eventType {
|
||||||
// Check for clients aliveness
|
case EventTick:
|
||||||
if daemon.lastAlivenessCheck.Add(AlivenessCheck).Before(now) {
|
for c := range clients {
|
||||||
for c := range daemon.clients {
|
if c.recvTimestamp.Add(PingTimeout).Before(now) {
|
||||||
aliveness, alive := daemon.clientAliveness[c]
|
|
||||||
if !alive {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if aliveness.timestamp.Add(PingTimeout).Before(now) {
|
|
||||||
log.Println(c, "ping timeout")
|
log.Println(c, "ping timeout")
|
||||||
c.conn.Close()
|
c.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !aliveness.pingSent && aliveness.timestamp.Add(PingThreshold).Before(now) {
|
if c.sendTimestamp.Add(PingThreshold).Before(now) {
|
||||||
if c.registered {
|
if c.registered {
|
||||||
c.Msg("PING :" + *daemon.hostname)
|
c.Msg("PING :" + *hostname)
|
||||||
aliveness.pingSent = true
|
c.sendTimestamp = time.Now()
|
||||||
} else {
|
} else {
|
||||||
log.Println(c, "ping timeout")
|
log.Println(c, "ping timeout")
|
||||||
c.conn.Close()
|
c.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
daemon.lastAlivenessCheck = now
|
case EventTerm:
|
||||||
}
|
for _, sink := range roomSinks {
|
||||||
|
sink <- ClientEvent{eventType: EventTerm}
|
||||||
switch event.eventType {
|
|
||||||
case EventNew:
|
|
||||||
daemon.clients[client] = struct{}{}
|
|
||||||
daemon.clientAliveness[client] = &ClientAlivenessState{
|
|
||||||
pingSent: false,
|
|
||||||
timestamp: now,
|
|
||||||
}
|
}
|
||||||
|
roomsGroup.Wait()
|
||||||
|
close(finished)
|
||||||
|
return
|
||||||
|
case EventNew:
|
||||||
|
clients[client] = struct{}{}
|
||||||
case EventDel:
|
case EventDel:
|
||||||
delete(daemon.clients, client)
|
delete(clients, client)
|
||||||
delete(daemon.clientAliveness, client)
|
for _, roomSink := range roomSinks {
|
||||||
for _, roomSink := range daemon.roomSinks {
|
|
||||||
roomSink <- event
|
roomSink <- event
|
||||||
}
|
}
|
||||||
case EventMsg:
|
case EventMsg:
|
||||||
cols := strings.SplitN(event.text, " ", 2)
|
cols := strings.SplitN(event.text, " ", 2)
|
||||||
command := strings.ToUpper(cols[0])
|
cmd := strings.ToUpper(cols[0])
|
||||||
if daemon.Verbose {
|
if *verbose {
|
||||||
log.Println(client, "command", command)
|
log.Println(client, "command", cmd)
|
||||||
}
|
}
|
||||||
if command == "QUIT" {
|
if cmd == "QUIT" {
|
||||||
log.Println(client, "quit")
|
log.Println(client, "quit")
|
||||||
delete(daemon.clients, client)
|
client.Close()
|
||||||
delete(daemon.clientAliveness, client)
|
|
||||||
client.conn.Close()
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !client.registered {
|
if !client.registered {
|
||||||
daemon.ClientRegister(client, command, cols)
|
ClientRegister(client, cmd, cols)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch command {
|
switch cmd {
|
||||||
case "AWAY":
|
case "AWAY":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.away = nil
|
client.away = nil
|
||||||
@ -378,63 +352,63 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
client.ReplyNotEnoughParameters("JOIN")
|
client.ReplyNotEnoughParameters("JOIN")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
daemon.HandlerJoin(client, cols[1])
|
HandlerJoin(client, cols[1])
|
||||||
case "LIST":
|
case "LIST":
|
||||||
daemon.SendList(client, cols)
|
SendList(client, cols)
|
||||||
case "LUSERS":
|
case "LUSERS":
|
||||||
daemon.SendLusers(client)
|
SendLusers(client)
|
||||||
case "MODE":
|
case "MODE":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("MODE")
|
client.ReplyNotEnoughParameters("MODE")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cols = strings.SplitN(cols[1], " ", 2)
|
cols = strings.SplitN(cols[1], " ", 2)
|
||||||
if cols[0] == client.username {
|
if cols[0] == *client.username {
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.Msg("221 " + client.nickname + " +")
|
client.Msg("221 " + *client.nickname + " +")
|
||||||
} else {
|
} else {
|
||||||
client.ReplyNicknamed("501", "Unknown MODE flag")
|
client.ReplyNicknamed("501", "Unknown MODE flag")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
room := cols[0]
|
room := cols[0]
|
||||||
r, found := daemon.rooms[room]
|
r, found := rooms[room]
|
||||||
if !found {
|
if !found {
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
daemon.roomSinks[r] <- ClientEvent{client, EventMode, ""}
|
roomSinks[r] <- ClientEvent{client, EventMode, ""}
|
||||||
} else {
|
} else {
|
||||||
daemon.roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
|
roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
|
||||||
}
|
}
|
||||||
case "MOTD":
|
case "MOTD":
|
||||||
go daemon.SendMotd(client)
|
SendMotd(client)
|
||||||
case "PART":
|
case "PART":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("PART")
|
client.ReplyNotEnoughParameters("PART")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rooms := strings.Split(cols[1], " ")[0]
|
rs := strings.Split(cols[1], " ")[0]
|
||||||
for _, room := range strings.Split(rooms, ",") {
|
for _, room := range strings.Split(rs, ",") {
|
||||||
r, found := daemon.rooms[room]
|
if r, found := rooms[room]; found {
|
||||||
if !found {
|
roomSinks[r] <- ClientEvent{client, EventDel, ""}
|
||||||
|
} else {
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
daemon.roomSinks[r] <- ClientEvent{client, EventDel, ""}
|
|
||||||
}
|
}
|
||||||
case "PING":
|
case "PING":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.ReplyNicknamed("409", "No origin specified")
|
client.ReplyNicknamed("409", "No origin specified")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
client.Reply(fmt.Sprintf("PONG %s :%s", *daemon.hostname, cols[1]))
|
client.Reply(fmt.Sprintf("PONG %s :%s", *hostname, cols[1]))
|
||||||
case "PONG":
|
case "PONG":
|
||||||
continue
|
continue
|
||||||
case "NOTICE", "PRIVMSG":
|
case "NOTICE", "PRIVMSG":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.ReplyNicknamed("411", "No recipient given ("+command+")")
|
client.ReplyNicknamed("411", "No recipient given ("+cmd+")")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cols = strings.SplitN(cols[1], " ", 2)
|
cols = strings.SplitN(cols[1], " ", 2)
|
||||||
@ -444,12 +418,12 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
}
|
}
|
||||||
msg := ""
|
msg := ""
|
||||||
target := strings.ToLower(cols[0])
|
target := strings.ToLower(cols[0])
|
||||||
for c := range daemon.clients {
|
for c := range clients {
|
||||||
if c.nickname == target {
|
if *c.nickname == target {
|
||||||
msg = fmt.Sprintf(":%s %s %s %s", client, command, c.nickname, cols[1])
|
msg = fmt.Sprintf(":%s %s %s %s", client, cmd, *c.nickname, cols[1])
|
||||||
c.Msg(msg)
|
c.Msg(msg)
|
||||||
if c.away != nil {
|
if c.away != nil {
|
||||||
client.ReplyNicknamed("301", c.nickname, *c.away)
|
client.ReplyNicknamed("301", *c.nickname, *c.away)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -457,15 +431,14 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
if msg != "" {
|
if msg != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r, found := daemon.rooms[target]
|
if r, found := rooms[target]; found {
|
||||||
if !found {
|
roomSinks[r] <- ClientEvent{
|
||||||
|
client,
|
||||||
|
EventMsg,
|
||||||
|
cmd + " " + strings.TrimLeft(cols[1], ":"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
client.ReplyNoNickChan(target)
|
client.ReplyNoNickChan(target)
|
||||||
continue
|
|
||||||
}
|
|
||||||
daemon.roomSinks[r] <- ClientEvent{
|
|
||||||
client,
|
|
||||||
EventMsg,
|
|
||||||
command + " " + strings.TrimLeft(cols[1], ":"),
|
|
||||||
}
|
}
|
||||||
case "TOPIC":
|
case "TOPIC":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
@ -473,7 +446,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cols = strings.SplitN(cols[1], " ", 2)
|
cols = strings.SplitN(cols[1], " ", 2)
|
||||||
r, found := daemon.rooms[cols[0]]
|
r, found := rooms[cols[0]]
|
||||||
if !found {
|
if !found {
|
||||||
client.ReplyNoChannel(cols[0])
|
client.ReplyNoChannel(cols[0])
|
||||||
continue
|
continue
|
||||||
@ -484,19 +457,18 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
} else {
|
} else {
|
||||||
change = ""
|
change = ""
|
||||||
}
|
}
|
||||||
daemon.roomSinks[r] <- ClientEvent{client, EventTopic, change}
|
roomSinks[r] <- ClientEvent{client, EventTopic, change}
|
||||||
case "WHO":
|
case "WHO":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("WHO")
|
client.ReplyNotEnoughParameters("WHO")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
room := strings.Split(cols[1], " ")[0]
|
room := strings.Split(cols[1], " ")[0]
|
||||||
r, found := daemon.rooms[room]
|
if r, found := rooms[room]; found {
|
||||||
if !found {
|
roomSinks[r] <- ClientEvent{client, EventWho, ""}
|
||||||
|
} else {
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
daemon.roomSinks[r] <- ClientEvent{client, EventWho, ""}
|
|
||||||
case "WHOIS":
|
case "WHOIS":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("WHOIS")
|
client.ReplyNotEnoughParameters("WHOIS")
|
||||||
@ -504,22 +476,21 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
}
|
}
|
||||||
cols := strings.Split(cols[1], " ")
|
cols := strings.Split(cols[1], " ")
|
||||||
nicknames := strings.Split(cols[len(cols)-1], ",")
|
nicknames := strings.Split(cols[len(cols)-1], ",")
|
||||||
daemon.SendWhois(client, nicknames)
|
SendWhois(client, nicknames)
|
||||||
case "VERSION":
|
case "VERSION":
|
||||||
var debug string
|
var debug string
|
||||||
if daemon.Verbose {
|
if *verbose {
|
||||||
debug = "debug"
|
debug = "debug"
|
||||||
} else {
|
} else {
|
||||||
debug = ""
|
debug = ""
|
||||||
}
|
}
|
||||||
client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", daemon.version, debug, *daemon.hostname))
|
client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", version, debug, *hostname))
|
||||||
default:
|
default:
|
||||||
client.ReplyNicknamed("421", command, "Unknown command")
|
client.ReplyNicknamed("421", cmd, "Unknown command")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if aliveness, alive := daemon.clientAliveness[client]; alive {
|
if client != nil {
|
||||||
aliveness.timestamp = now
|
client.recvTimestamp = now
|
||||||
aliveness.pingSent = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,21 +27,24 @@ import (
|
|||||||
|
|
||||||
func TestRegistrationWorkflow(t *testing.T) {
|
func TestRegistrationWorkflow(t *testing.T) {
|
||||||
host := "foohost"
|
host := "foohost"
|
||||||
daemon := NewDaemon("ver1", &host, nil, nil, nil, nil)
|
hostname = &host
|
||||||
events := make(chan ClientEvent)
|
events := make(chan ClientEvent)
|
||||||
go daemon.Processor(events)
|
defer func() {
|
||||||
|
events <- ClientEvent{eventType: EventTerm}
|
||||||
|
}()
|
||||||
|
go Processor(events, make(chan struct{}))
|
||||||
conn := NewTestingConn()
|
conn := NewTestingConn()
|
||||||
client := NewClient(&host, conn)
|
client := NewClient(conn)
|
||||||
go client.Processor(events)
|
go client.Processor(events)
|
||||||
|
|
||||||
conn.inbound <- "UNEXISTENT CMD" // should recieve nothing on this
|
conn.inbound <- "UNEXISTENT CMD" // should receive nothing on this
|
||||||
conn.inbound <- "NICK"
|
conn.inbound <- "NICK"
|
||||||
|
|
||||||
if r := <-conn.outbound; r != ":foohost 431 :No nickname given\r\n" {
|
if r := <-conn.outbound; r != ":foohost 431 :No nickname given\r\n" {
|
||||||
t.Fatal("431 for NICK", r)
|
t.Fatal("431 for NICK", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, n := range []string{"привет", " foo", "longlonglong", "#foo", "mein nick", "foo_bar"} {
|
for _, n := range []string{"привет", " foo", "#foo", "mein nick", "foo_bar"} {
|
||||||
conn.inbound <- "NICK " + n
|
conn.inbound <- "NICK " + n
|
||||||
if r := <-conn.outbound; r != ":foohost 432 * "+n+" :Erroneous nickname\r\n" {
|
if r := <-conn.outbound; r != ":foohost 432 * "+n+" :Erroneous nickname\r\n" {
|
||||||
t.Fatal("nickname validation", r)
|
t.Fatal("nickname validation", r)
|
||||||
@ -52,7 +55,7 @@ func TestRegistrationWorkflow(t *testing.T) {
|
|||||||
if r := <-conn.outbound; r != ":foohost 461 meinick USER :Not enough parameters\r\n" {
|
if r := <-conn.outbound; r != ":foohost 461 meinick USER :Not enough parameters\r\n" {
|
||||||
t.Fatal("461 for USER", r)
|
t.Fatal("461 for USER", r)
|
||||||
}
|
}
|
||||||
if (client.nickname != "meinick") || client.registered {
|
if (*client.nickname != "meinick") || client.registered {
|
||||||
t.Fatal("NICK saved")
|
t.Fatal("NICK saved")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ func TestRegistrationWorkflow(t *testing.T) {
|
|||||||
t.Fatal("461 again for USER", r)
|
t.Fatal("461 again for USER", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon.SendLusers(client)
|
SendLusers(client)
|
||||||
if r := <-conn.outbound; !strings.Contains(r, "There are 0 users") {
|
if r := <-conn.outbound; !strings.Contains(r, "There are 0 users") {
|
||||||
t.Fatal("LUSERS", r)
|
t.Fatal("LUSERS", r)
|
||||||
}
|
}
|
||||||
@ -85,7 +88,7 @@ func TestRegistrationWorkflow(t *testing.T) {
|
|||||||
if r := <-conn.outbound; !strings.Contains(r, ":foohost 422") {
|
if r := <-conn.outbound; !strings.Contains(r, ":foohost 422") {
|
||||||
t.Fatal("422 after registration", r)
|
t.Fatal("422 after registration", r)
|
||||||
}
|
}
|
||||||
if (client.username != "1") || (client.realname != "4 5") || !client.registered {
|
if (*client.username != "1") || (*client.realname != "4 5") || !client.registered {
|
||||||
t.Fatal("client register")
|
t.Fatal("client register")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +99,7 @@ func TestRegistrationWorkflow(t *testing.T) {
|
|||||||
t.Fatal("reply for unexistent command", r)
|
t.Fatal("reply for unexistent command", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon.SendLusers(client)
|
SendLusers(client)
|
||||||
if r := <-conn.outbound; !strings.Contains(r, "There are 1 users") {
|
if r := <-conn.outbound; !strings.Contains(r, "There are 1 users") {
|
||||||
t.Fatal("1 users logged in", r)
|
t.Fatal("1 users logged in", r)
|
||||||
}
|
}
|
||||||
@ -107,10 +110,6 @@ func TestRegistrationWorkflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn.inbound <- "QUIT\r\nUNEXISTENT CMD"
|
conn.inbound <- "QUIT\r\nUNEXISTENT CMD"
|
||||||
<-conn.outbound
|
|
||||||
if !conn.closed {
|
|
||||||
t.Fatal("closed connection on QUIT")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMotd(t *testing.T) {
|
func TestMotd(t *testing.T) {
|
||||||
@ -123,11 +122,12 @@ func TestMotd(t *testing.T) {
|
|||||||
|
|
||||||
conn := NewTestingConn()
|
conn := NewTestingConn()
|
||||||
host := "foohost"
|
host := "foohost"
|
||||||
client := NewClient(&host, conn)
|
hostname = &host
|
||||||
|
client := NewClient(conn)
|
||||||
motdName := fd.Name()
|
motdName := fd.Name()
|
||||||
daemon := NewDaemon("ver1", &host, &motdName, nil, nil, nil)
|
motd = &motdName
|
||||||
|
|
||||||
daemon.SendMotd(client)
|
SendMotd(client)
|
||||||
if r := <-conn.outbound; !strings.HasPrefix(r, ":foohost 375") {
|
if r := <-conn.outbound; !strings.HasPrefix(r, ":foohost 375") {
|
||||||
t.Fatal("MOTD start", r)
|
t.Fatal("MOTD start", r)
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,17 @@ const (
|
|||||||
EventTopic = iota
|
EventTopic = iota
|
||||||
EventWho = iota
|
EventWho = iota
|
||||||
EventMode = iota
|
EventMode = iota
|
||||||
|
EventTerm = iota
|
||||||
|
EventTick = iota
|
||||||
FormatMsg = "[%s] <%s> %s\n"
|
FormatMsg = "[%s] <%s> %s\n"
|
||||||
FormatMeta = "[%s] * %s %s\n"
|
FormatMeta = "[%s] * %s %s\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logSink chan LogEvent = make(chan LogEvent)
|
||||||
|
stateSink chan StateEvent = make(chan StateEvent)
|
||||||
|
)
|
||||||
|
|
||||||
// Client events going from each of client
|
// Client events going from each of client
|
||||||
// They can be either NEW, DEL or unparsed MSG
|
// They can be either NEW, DEL or unparsed MSG
|
||||||
type ClientEvent struct {
|
type ClientEvent struct {
|
||||||
@ -70,7 +77,7 @@ func Logger(logdir string, events <-chan LogEvent) {
|
|||||||
var fd *os.File
|
var fd *os.File
|
||||||
var err error
|
var err error
|
||||||
for event := range events {
|
for event := range events {
|
||||||
logfile = path.Join(logdir, event.where + ".log")
|
logfile = path.Join(logdir, event.where+".log")
|
||||||
fd, err = os.OpenFile(logfile, mode, perm)
|
fd, err = os.OpenFile(logfile, mode, perm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Can not open logfile", logfile, err)
|
log.Println("Can not open logfile", logfile, err)
|
||||||
|
31
goircd.go
31
goircd.go
@ -37,21 +37,19 @@ var (
|
|||||||
logdir = flag.String("logdir", "", "Absolute path to directory for logs")
|
logdir = flag.String("logdir", "", "Absolute path to directory for logs")
|
||||||
statedir = flag.String("statedir", "", "Absolute path to directory for states")
|
statedir = flag.String("statedir", "", "Absolute path to directory for states")
|
||||||
passwords = flag.String("passwords", "", "Optional path to passwords file")
|
passwords = flag.String("passwords", "", "Optional path to passwords file")
|
||||||
|
tlsBind = flag.String("tlsbind", "", "TLS address to bind to")
|
||||||
tlsBind = flag.String("tlsbind", "", "TLS address to bind to")
|
tlsPEM = flag.String("tlspem", "", "Path to TLS certificat+key PEM file")
|
||||||
tlsPEM = flag.String("tlspem", "", "Path to TLS certificat+key PEM file")
|
verbose = flag.Bool("v", false, "Enable verbose logging.")
|
||||||
|
|
||||||
verbose = flag.Bool("v", false, "Enable verbose logging.")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func listenerLoop(sock net.Listener, events chan<- ClientEvent) {
|
func listenerLoop(sock net.Listener, events chan ClientEvent) {
|
||||||
for {
|
for {
|
||||||
conn, err := sock.Accept()
|
conn, err := sock.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error during accepting connection", err)
|
log.Println("Error during accepting connection", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
client := NewClient(hostname, conn)
|
client := NewClient(conn)
|
||||||
go client.Processor(events)
|
go client.Processor(events)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +58,6 @@ func Run() {
|
|||||||
events := make(chan ClientEvent)
|
events := make(chan ClientEvent)
|
||||||
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
|
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
|
||||||
|
|
||||||
logSink := make(chan LogEvent)
|
|
||||||
if *logdir == "" {
|
if *logdir == "" {
|
||||||
// Dummy logger
|
// Dummy logger
|
||||||
go func() {
|
go func() {
|
||||||
@ -75,10 +72,7 @@ func Run() {
|
|||||||
log.Println(*logdir, "logger initialized")
|
log.Println(*logdir, "logger initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
stateSink := make(chan StateEvent)
|
log.Println("goircd " + version + " is starting")
|
||||||
daemon := NewDaemon(version, hostname, motd, passwords, logSink, stateSink)
|
|
||||||
daemon.Verbose = *verbose
|
|
||||||
log.Println("goircd " + daemon.version + " is starting")
|
|
||||||
if *statedir == "" {
|
if *statedir == "" {
|
||||||
// Dummy statekeeper
|
// Dummy statekeeper
|
||||||
go func() {
|
go func() {
|
||||||
@ -98,14 +92,14 @@ func Run() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Can not read state %s: %v", state, err)
|
log.Fatalf("Can not read state %s: %v", state, err)
|
||||||
}
|
}
|
||||||
room, _ := daemon.RoomRegister(path.Base(state))
|
room, _ := RoomRegister(path.Base(state))
|
||||||
contents := strings.Split(string(buf), "\n")
|
contents := strings.Split(string(buf), "\n")
|
||||||
if len(contents) < 2 {
|
if len(contents) < 2 {
|
||||||
log.Printf("State corrupted for %s: %q", room.name, contents)
|
log.Printf("State corrupted for %s: %q", *room.name, contents)
|
||||||
} else {
|
} else {
|
||||||
room.topic = contents[0]
|
room.topic = &contents[0]
|
||||||
room.key = contents[1]
|
room.key = &contents[1]
|
||||||
log.Println("Loaded state for room", room.name)
|
log.Println("Loaded state for room", *room.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
go StateKeeper(*statedir, stateSink)
|
go StateKeeper(*statedir, stateSink)
|
||||||
@ -133,8 +127,7 @@ func Run() {
|
|||||||
log.Println("TLS listening on", *tlsBind)
|
log.Println("TLS listening on", *tlsBind)
|
||||||
go listenerLoop(listenerTLS, events)
|
go listenerLoop(listenerTLS, events)
|
||||||
}
|
}
|
||||||
|
Processor(events, make(chan struct{}))
|
||||||
daemon.Processor(events)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
144
room.go
144
room.go
@ -28,6 +28,10 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
|
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
|
||||||
|
|
||||||
|
rooms map[string]*Room = make(map[string]*Room)
|
||||||
|
|
||||||
|
roomSinks map[*Room]chan ClientEvent = make(map[*Room]chan ClientEvent)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sanitize room's name. It can consist of 1 to 50 ASCII symbols
|
// Sanitize room's name. It can consist of 1 to 50 ASCII symbols
|
||||||
@ -37,40 +41,34 @@ func RoomNameValid(name string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Room struct {
|
type Room struct {
|
||||||
Verbose bool
|
name *string
|
||||||
name string
|
topic *string
|
||||||
topic string
|
key *string
|
||||||
key string
|
members map[*Client]struct{}
|
||||||
members map[*Client]bool
|
|
||||||
hostname *string
|
|
||||||
logSink chan<- LogEvent
|
|
||||||
stateSink chan<- StateEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room Room) String() string {
|
func (room Room) String() string {
|
||||||
return room.name
|
return *room.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoom(hostname *string, name string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Room {
|
func NewRoom(name string) *Room {
|
||||||
room := Room{name: name}
|
topic := ""
|
||||||
room.members = make(map[*Client]bool)
|
return &Room{
|
||||||
room.topic = ""
|
name: &name,
|
||||||
room.key = ""
|
topic: &topic,
|
||||||
room.hostname = hostname
|
members: make(map[*Client]struct{}),
|
||||||
room.logSink = logSink
|
|
||||||
room.stateSink = stateSink
|
|
||||||
return &room
|
|
||||||
}
|
|
||||||
|
|
||||||
func (room *Room) SendTopic(client *Client) {
|
|
||||||
if room.topic == "" {
|
|
||||||
client.ReplyNicknamed("331", room.name, "No topic is set")
|
|
||||||
} else {
|
|
||||||
client.ReplyNicknamed("332", room.name, room.topic)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message to all room's subscribers, possibly excluding someone
|
func (room *Room) SendTopic(client *Client) {
|
||||||
|
if *room.topic == "" {
|
||||||
|
client.ReplyNicknamed("331", *room.name, "No topic is set")
|
||||||
|
} else {
|
||||||
|
client.ReplyNicknamed("332", *room.name, *room.topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send message to all room's subscribers, possibly excluding someone.
|
||||||
func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
|
func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
|
||||||
for member := range room.members {
|
for member := range room.members {
|
||||||
if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
|
if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
|
||||||
@ -81,7 +79,11 @@ func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) StateSave() {
|
func (room *Room) StateSave() {
|
||||||
room.stateSink <- StateEvent{room.name, room.topic, room.key}
|
var key string
|
||||||
|
if room.key != nil {
|
||||||
|
key = *room.key
|
||||||
|
}
|
||||||
|
stateSink <- StateEvent{*room.name, *room.topic, key}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) Processor(events <-chan ClientEvent) {
|
func (room *Room) Processor(events <-chan ClientEvent) {
|
||||||
@ -89,46 +91,50 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
for event := range events {
|
for event := range events {
|
||||||
client = event.client
|
client = event.client
|
||||||
switch event.eventType {
|
switch event.eventType {
|
||||||
|
case EventTerm:
|
||||||
|
roomsGroup.Done()
|
||||||
|
return
|
||||||
case EventNew:
|
case EventNew:
|
||||||
room.members[client] = true
|
room.members[client] = struct{}{}
|
||||||
if room.Verbose {
|
if *verbose {
|
||||||
log.Println(client, "joined", room.name)
|
log.Println(client, "joined", room.name)
|
||||||
}
|
}
|
||||||
room.SendTopic(client)
|
room.SendTopic(client)
|
||||||
room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.name))
|
room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, *room.name))
|
||||||
room.logSink <- LogEvent{room.name, client.nickname, "joined", true}
|
logSink <- LogEvent{*room.name, *client.nickname, "joined", true}
|
||||||
nicknames := []string{}
|
nicknames := make([]string, 0)
|
||||||
for member := range room.members {
|
for member := range room.members {
|
||||||
nicknames = append(nicknames, member.nickname)
|
nicknames = append(nicknames, *member.nickname)
|
||||||
}
|
}
|
||||||
sort.Strings(nicknames)
|
sort.Strings(nicknames)
|
||||||
client.ReplyNicknamed("353", "=", room.name, strings.Join(nicknames, " "))
|
client.ReplyNicknamed("353", "=", *room.name, strings.Join(nicknames, " "))
|
||||||
client.ReplyNicknamed("366", room.name, "End of NAMES list")
|
client.ReplyNicknamed("366", *room.name, "End of NAMES list")
|
||||||
case EventDel:
|
case EventDel:
|
||||||
if _, subscribed := room.members[client]; !subscribed {
|
if _, subscribed := room.members[client]; !subscribed {
|
||||||
client.ReplyNicknamed("442", room.name, "You are not on that channel")
|
client.ReplyNicknamed("442", *room.name, "You are not on that channel")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delete(room.members, client)
|
delete(room.members, client)
|
||||||
msg := fmt.Sprintf(":%s PART %s :%s", client, room.name, client.nickname)
|
msg := fmt.Sprintf(":%s PART %s :%s", client, *room.name, *client.nickname)
|
||||||
room.Broadcast(msg)
|
room.Broadcast(msg)
|
||||||
room.logSink <- LogEvent{room.name, client.nickname, "left", true}
|
logSink <- LogEvent{*room.name, *client.nickname, "left", true}
|
||||||
case EventTopic:
|
case EventTopic:
|
||||||
if _, subscribed := room.members[client]; !subscribed {
|
if _, subscribed := room.members[client]; !subscribed {
|
||||||
client.ReplyParts("442", room.name, "You are not on that channel")
|
client.ReplyParts("442", *room.name, "You are not on that channel")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if event.text == "" {
|
if event.text == "" {
|
||||||
go room.SendTopic(client)
|
room.SendTopic(client)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
room.topic = strings.TrimLeft(event.text, ":")
|
topic := strings.TrimLeft(event.text, ":")
|
||||||
msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.name, room.topic)
|
room.topic = &topic
|
||||||
go room.Broadcast(msg)
|
msg := fmt.Sprintf(":%s TOPIC %s :%s", client, *room.name, *room.topic)
|
||||||
room.logSink <- LogEvent{
|
room.Broadcast(msg)
|
||||||
room.name,
|
logSink <- LogEvent{
|
||||||
client.nickname,
|
*room.name,
|
||||||
"set topic to " + room.topic,
|
*client.nickname,
|
||||||
|
"set topic to " + *room.topic,
|
||||||
true,
|
true,
|
||||||
}
|
}
|
||||||
room.StateSave()
|
room.StateSave()
|
||||||
@ -136,32 +142,32 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
for m := range room.members {
|
for m := range room.members {
|
||||||
client.ReplyNicknamed(
|
client.ReplyNicknamed(
|
||||||
"352",
|
"352",
|
||||||
room.name,
|
*room.name,
|
||||||
m.username,
|
*m.username,
|
||||||
m.conn.RemoteAddr().String(),
|
m.conn.RemoteAddr().String(),
|
||||||
*room.hostname,
|
*hostname,
|
||||||
m.nickname,
|
*m.nickname,
|
||||||
"H",
|
"H",
|
||||||
"0 "+m.realname,
|
"0 "+*m.realname,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
client.ReplyNicknamed("315", room.name, "End of /WHO list")
|
client.ReplyNicknamed("315", *room.name, "End of /WHO list")
|
||||||
case EventMode:
|
case EventMode:
|
||||||
if event.text == "" {
|
if event.text == "" {
|
||||||
mode := "+"
|
mode := "+"
|
||||||
if room.key != "" {
|
if room.key != nil {
|
||||||
mode = mode + "k"
|
mode = mode + "k"
|
||||||
}
|
}
|
||||||
client.Msg(fmt.Sprintf("324 %s %s %s", client.nickname, room.name, mode))
|
client.Msg(fmt.Sprintf("324 %s %s %s", *client.nickname, *room.name, mode))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(event.text, "b") {
|
if strings.HasPrefix(event.text, "b") {
|
||||||
client.ReplyNicknamed("368", room.name, "End of channel ban list")
|
client.ReplyNicknamed("368", *room.name, "End of channel ban list")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
|
if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
|
||||||
if _, subscribed := room.members[client]; !subscribed {
|
if _, subscribed := room.members[client]; !subscribed {
|
||||||
client.ReplyParts("442", room.name, "You are not on that channel")
|
client.ReplyParts("442", *room.name, "You are not on that channel")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -176,16 +182,16 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
client.ReplyNotEnoughParameters("MODE")
|
client.ReplyNotEnoughParameters("MODE")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
room.key = cols[1]
|
room.key = &cols[1]
|
||||||
msg = fmt.Sprintf(":%s MODE %s +k %s", client, room.name, room.key)
|
msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key)
|
||||||
msgLog = "set channel key to " + room.key
|
msgLog = "set channel key to " + *room.key
|
||||||
} else if strings.HasPrefix(event.text, "-k") {
|
} else {
|
||||||
room.key = ""
|
room.key = nil
|
||||||
msg = fmt.Sprintf(":%s MODE %s -k", client, room.name)
|
msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name)
|
||||||
msgLog = "removed channel key"
|
msgLog = "removed channel key"
|
||||||
}
|
}
|
||||||
go room.Broadcast(msg)
|
room.Broadcast(msg)
|
||||||
room.logSink <- LogEvent{room.name, client.nickname, msgLog, true}
|
logSink <- LogEvent{*room.name, *client.nickname, msgLog, true}
|
||||||
room.StateSave()
|
room.StateSave()
|
||||||
case EventMsg:
|
case EventMsg:
|
||||||
sep := strings.Index(event.text, " ")
|
sep := strings.Index(event.text, " ")
|
||||||
@ -193,13 +199,13 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
":%s %s %s :%s",
|
":%s %s %s :%s",
|
||||||
client,
|
client,
|
||||||
event.text[:sep],
|
event.text[:sep],
|
||||||
room.name,
|
*room.name,
|
||||||
event.text[sep+1:]),
|
event.text[sep+1:]),
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
room.logSink <- LogEvent{
|
logSink <- LogEvent{
|
||||||
room.name,
|
*room.name,
|
||||||
client.nickname,
|
*client.nickname,
|
||||||
event.text[sep+1:],
|
event.text[sep+1:],
|
||||||
false,
|
false,
|
||||||
}
|
}
|
||||||
|
70
room_test.go
70
room_test.go
@ -42,17 +42,25 @@ func notEnoughParams(t *testing.T, c *TestingConn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTwoUsers(t *testing.T) {
|
func TestTwoUsers(t *testing.T) {
|
||||||
logSink := make(chan LogEvent, 8)
|
logSink = make(chan LogEvent, 8)
|
||||||
stateSink := make(chan StateEvent, 8)
|
stateSink = make(chan StateEvent, 8)
|
||||||
host := "foohost"
|
host := "foohost"
|
||||||
daemon := NewDaemon("ver1", &host, nil, nil, logSink, stateSink)
|
hostname = &host
|
||||||
events := make(chan ClientEvent)
|
events := make(chan ClientEvent)
|
||||||
go daemon.Processor(events)
|
rooms = make(map[string]*Room)
|
||||||
|
clients = make(map[*Client]struct{})
|
||||||
|
roomSinks = make(map[*Room]chan ClientEvent)
|
||||||
|
finished := make(chan struct{})
|
||||||
|
go Processor(events, finished)
|
||||||
|
defer func() {
|
||||||
|
events <- ClientEvent{eventType: EventTerm}
|
||||||
|
<-finished
|
||||||
|
}()
|
||||||
|
|
||||||
conn1 := NewTestingConn()
|
conn1 := NewTestingConn()
|
||||||
conn2 := NewTestingConn()
|
conn2 := NewTestingConn()
|
||||||
client1 := NewClient(&host, conn1)
|
client1 := NewClient(conn1)
|
||||||
client2 := NewClient(&host, conn2)
|
client2 := NewClient(conn2)
|
||||||
go client1.Processor(events)
|
go client1.Processor(events)
|
||||||
go client2.Processor(events)
|
go client2.Processor(events)
|
||||||
|
|
||||||
@ -63,7 +71,7 @@ func TestTwoUsers(t *testing.T) {
|
|||||||
<-conn2.outbound
|
<-conn2.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon.SendLusers(client1)
|
SendLusers(client1)
|
||||||
if r := <-conn1.outbound; !strings.Contains(r, "There are 2 users") {
|
if r := <-conn1.outbound; !strings.Contains(r, "There are 2 users") {
|
||||||
t.Fatal("LUSERS", r)
|
t.Fatal("LUSERS", r)
|
||||||
}
|
}
|
||||||
@ -105,27 +113,40 @@ func TestTwoUsers(t *testing.T) {
|
|||||||
conn1.inbound <- "PRIVMSG nick2 :Hello"
|
conn1.inbound <- "PRIVMSG nick2 :Hello"
|
||||||
conn1.inbound <- "PRIVMSG #foo :world"
|
conn1.inbound <- "PRIVMSG #foo :world"
|
||||||
conn1.inbound <- "NOTICE #foo :world"
|
conn1.inbound <- "NOTICE #foo :world"
|
||||||
<-conn2.outbound
|
m1 := <-conn2.outbound
|
||||||
if r := <-conn2.outbound; r != ":nick1!foo1@someclient PRIVMSG nick2 :Hello\r\n" {
|
m2 := <-conn2.outbound
|
||||||
t.Fatal("first message", r)
|
mNeeded := ":nick1!foo1@someclient PRIVMSG nick2 :Hello\r\n"
|
||||||
|
if !(m1 == mNeeded || m2 == mNeeded) {
|
||||||
|
t.Fatal("first message", m1, m2)
|
||||||
}
|
}
|
||||||
if r := <-conn2.outbound; r != ":nick1!foo1@someclient PRIVMSG #foo :world\r\n" {
|
if m2 == mNeeded {
|
||||||
t.Fatal("second message", r)
|
m2 = <-conn2.outbound
|
||||||
}
|
}
|
||||||
if r := <-conn2.outbound; r != ":nick1!foo1@someclient NOTICE #foo :world\r\n" {
|
if m2 != ":nick1!foo1@someclient PRIVMSG #foo :world\r\n" {
|
||||||
t.Fatal("third message", r)
|
t.Fatal("second message", m2)
|
||||||
|
}
|
||||||
|
if m2 = <-conn2.outbound; m2 != ":nick1!foo1@someclient NOTICE #foo :world\r\n" {
|
||||||
|
t.Fatal("third message", m2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJoin(t *testing.T) {
|
func TestJoin(t *testing.T) {
|
||||||
logSink := make(chan LogEvent, 8)
|
logSink = make(chan LogEvent, 8)
|
||||||
stateSink := make(chan StateEvent, 8)
|
stateSink = make(chan StateEvent, 8)
|
||||||
host := "foohost"
|
host := "foohost"
|
||||||
daemon := NewDaemon("ver1", &host, nil, nil, logSink, stateSink)
|
hostname = &host
|
||||||
events := make(chan ClientEvent)
|
events := make(chan ClientEvent)
|
||||||
go daemon.Processor(events)
|
rooms = make(map[string]*Room)
|
||||||
|
clients = make(map[*Client]struct{})
|
||||||
|
roomSinks = make(map[*Room]chan ClientEvent)
|
||||||
|
finished := make(chan struct{})
|
||||||
|
go Processor(events, finished)
|
||||||
|
defer func() {
|
||||||
|
events <- ClientEvent{eventType: EventTerm}
|
||||||
|
<-finished
|
||||||
|
}()
|
||||||
conn := NewTestingConn()
|
conn := NewTestingConn()
|
||||||
client := NewClient(&host, conn)
|
client := NewClient(conn)
|
||||||
go client.Processor(events)
|
go client.Processor(events)
|
||||||
|
|
||||||
conn.inbound <- "NICK nick2\r\nUSER foo2 bar2 baz2 :Long name2"
|
conn.inbound <- "NICK nick2\r\nUSER foo2 bar2 baz2 :Long name2"
|
||||||
@ -161,10 +182,10 @@ func TestJoin(t *testing.T) {
|
|||||||
for i := 0; i < 4*2; i++ {
|
for i := 0; i < 4*2; i++ {
|
||||||
<-conn.outbound
|
<-conn.outbound
|
||||||
}
|
}
|
||||||
if _, ok := daemon.rooms["#bar"]; !ok {
|
if _, ok := rooms["#bar"]; !ok {
|
||||||
t.Fatal("#bar does not exist")
|
t.Fatal("#bar does not exist")
|
||||||
}
|
}
|
||||||
if _, ok := daemon.rooms["#baz"]; !ok {
|
if _, ok := rooms["#baz"]; !ok {
|
||||||
t.Fatal("#baz does not exist")
|
t.Fatal("#baz does not exist")
|
||||||
}
|
}
|
||||||
if r := <-logSink; (r.what != "joined") || (r.where != "#bar") || (r.who != "nick2") || (r.meta != true) {
|
if r := <-logSink; (r.what != "joined") || (r.where != "#bar") || (r.who != "nick2") || (r.meta != true) {
|
||||||
@ -178,10 +199,10 @@ func TestJoin(t *testing.T) {
|
|||||||
for i := 0; i < 4*2; i++ {
|
for i := 0; i < 4*2; i++ {
|
||||||
<-conn.outbound
|
<-conn.outbound
|
||||||
}
|
}
|
||||||
if daemon.rooms["#barenc"].key != "key1" {
|
if *rooms["#barenc"].key != "key1" {
|
||||||
t.Fatal("no room with key1")
|
t.Fatal("no room with key1")
|
||||||
}
|
}
|
||||||
if daemon.rooms["#bazenc"].key != "key2" {
|
if *rooms["#bazenc"].key != "key2" {
|
||||||
t.Fatal("no room with key2")
|
t.Fatal("no room with key2")
|
||||||
}
|
}
|
||||||
if r := <-logSink; (r.what != "joined") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
if r := <-logSink; (r.what != "joined") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
||||||
@ -201,7 +222,7 @@ func TestJoin(t *testing.T) {
|
|||||||
if r := <-conn.outbound; r != ":nick2!foo2@someclient MODE #barenc -k\r\n" {
|
if r := <-conn.outbound; r != ":nick2!foo2@someclient MODE #barenc -k\r\n" {
|
||||||
t.Fatal("remove #barenc key", r)
|
t.Fatal("remove #barenc key", r)
|
||||||
}
|
}
|
||||||
if daemon.rooms["#barenc"].key != "" {
|
if rooms["#barenc"].key != nil {
|
||||||
t.Fatal("removing key from #barenc")
|
t.Fatal("removing key from #barenc")
|
||||||
}
|
}
|
||||||
if r := <-logSink; (r.what != "removed channel key") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
if r := <-logSink; (r.what != "removed channel key") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
||||||
@ -253,5 +274,4 @@ func TestJoin(t *testing.T) {
|
|||||||
if r := <-conn.outbound; r != ":foohost 315 nick2 #barenc :End of /WHO list\r\n" {
|
if r := <-conn.outbound; r != ":foohost 315 nick2 #barenc :End of /WHO list\r\n" {
|
||||||
t.Fatal("end of WHO", r)
|
t.Fatal("end of WHO", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user