/*
goircd -- minimalistic simple Internet Relay Chat (IRC) server
-Copyright (C) 2014 Sergey Matveev <stargrave@stargrave.org>
+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
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 (
"log"
"net"
"strings"
+ "sync"
"time"
)
const (
- CRLF = "\x0d\x0a"
- BUF_SIZE = 1380
+ BufSize = 1500
+ MaxOutBuf = 1 << 12
+)
+
+var (
+ CRLF []byte = []byte{'\x0d', '\x0a'}
)
type Client struct {
- hostname string
- conn net.Conn
- registered bool
- ping_sent bool
- timestamp time.Time
- nickname string
- username string
- realname string
+ conn net.Conn
+ registered bool
+ nickname *string
+ username *string
+ realname *string
+ password *string
+ away *string
+ recvTimestamp time.Time
+ sendTimestamp time.Time
+ outBuf chan string
+ alive bool
+ sync.Mutex
+}
+
+func (c Client) Host() string {
+ addr := c.conn.RemoteAddr().String()
+ if host, _, err := net.SplitHostPort(addr); err == nil {
+ addr = host
+ }
+ if domains, err := net.LookupAddr(addr); err == nil {
+ addr = strings.TrimSuffix(domains[0], ".")
+ }
+ return addr
+}
+
+func (c Client) String() string {
+ return *c.nickname + "!" + *c.username + "@" + c.Host()
}
-func (client Client) String() string {
- return client.nickname + "!" + client.username + "@" + client.conn.RemoteAddr().String()
+func NewClient(conn net.Conn) *Client {
+ 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 {
- return &Client{hostname: hostname, conn: conn, nickname: "*"}
+func (c *Client) SetDead() {
+ 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,
// splits messages by CRLF and send them to Daemon gorouting for processing
// it futher. Also it can signalize that client is unavailable (disconnected).
-func (client *Client) Processor(sink chan ClientEvent) {
- var buf_net []byte
- buf := make([]byte, 0)
- log.Println("New client", client)
- sink <- ClientEvent{client, EVENT_NEW, ""}
+func (c *Client) Processor(sink chan ClientEvent) {
+ sink <- ClientEvent{c, EventNew, ""}
+ log.Println(c, "New client")
+ buf := make([]byte, BufSize*2)
+ var n int
+ var prev int
+ var i int
+ var err error
for {
- buf_net = make([]byte, BUF_SIZE)
- _, err := client.conn.Read(buf_net)
+ if prev == BufSize {
+ log.Println(c, "input buffer size exceeded, kicking him")
+ break
+ }
+ n, err = c.conn.Read(buf[prev:])
if err != nil {
- log.Println(client, "connection lost", err)
- sink <- ClientEvent{client, EVENT_DEL, ""}
break
}
- client.timestamp = time.Now()
- client.ping_sent = false
- buf_net = bytes.TrimRight(buf_net, "\x00")
- buf = append(buf, buf_net...)
- if !bytes.HasSuffix(buf, []byte(CRLF)) {
+ prev += n
+ CheckMore:
+ i = bytes.Index(buf[:prev], CRLF)
+ if i == -1 {
continue
}
- for _, msg := range bytes.Split(buf[:len(buf)-2], []byte(CRLF)) {
- if len(msg) > 0 {
- sink <- ClientEvent{client, EVENT_MSG, string(msg)}
- }
- }
- buf = []byte{}
+ sink <- ClientEvent{c, EventMsg, string(buf[:i])}
+ copy(buf, buf[i+2:prev])
+ prev -= (i + 2)
+ 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.
-func (client *Client) Msg(text string) {
- client.conn.Write([]byte(text + CRLF))
+func (c *Client) Msg(text string) {
+ 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.
-func (client *Client) Reply(text string) {
- client.Msg(":" + client.hostname + " " + text)
+func (c *Client) Reply(text string) {
+ c.Msg(":" + *hostname + " " + text)
}
// Send server message, concatenating all provided text parts and
// prefix the last one with ":".
-func (client *Client) ReplyParts(code string, text ...string) {
+func (c *Client) ReplyParts(code string, text ...string) {
parts := []string{code}
for _, t := range text {
parts = append(parts, t)
}
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
// client's nickname. The last part is prefixed with ":".
-func (client *Client) ReplyNicknamed(code string, text ...string) {
- client.ReplyParts(code, append([]string{client.nickname}, text...)...)
+func (c *Client) ReplyNicknamed(code string, text ...string) {
+ c.ReplyParts(code, append([]string{*c.nickname}, text...)...)
}
// Reply "461 not enough parameters" error for given command.
-func (client *Client) ReplyNotEnoughParameters(command string) {
- client.ReplyNicknamed("461", command, "Not enough parameters")
+func (c *Client) ReplyNotEnoughParameters(command string) {
+ c.ReplyNicknamed("461", command, "Not enough parameters")
}
// Reply "403 no such channel" error for specified channel.
-func (client *Client) ReplyNoChannel(channel string) {
- client.ReplyNicknamed("403", channel, "No such channel")
+func (c *Client) ReplyNoChannel(channel string) {
+ c.ReplyNicknamed("403", channel, "No such channel")
+}
+
+func (c *Client) ReplyNoNickChan(channel string) {
+ c.ReplyNicknamed("401", channel, "No such nick/channel")
}