goircd/client.go

181 lines
4.2 KiB
Go
Raw Normal View History

2014-05-11 16:18:55 +00:00
/*
goircd -- minimalistic simple Internet Relay Chat (IRC) server
2015-05-09 15:27:06 +00:00
Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
2014-05-11 16:18:55 +00:00
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/>.
*/
2015-05-09 15:27:06 +00:00
2014-05-11 16:18:55 +00:00
package main
import (
"bytes"
"log"
"strings"
"sync"
2014-05-11 16:18:55 +00:00
"time"
proxyproto "github.com/Freeaqingme/go-proxyproto"
2014-05-11 16:18:55 +00:00
)
const (
BufSize = 1500
MaxOutBuf = 1 << 12
)
var (
CRLF []byte = []byte{'\x0d', '\x0a'}
2014-05-11 16:18:55 +00:00
)
type Client struct {
conn *proxyproto.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
2014-05-11 16:18:55 +00:00
}
func (c Client) String() string {
return *c.nickname + "!" + *c.username + "@" + c.conn.RemoteAddr().String()
2014-08-09 11:37:10 +00:00
}
func NewClient(conn *proxyproto.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
2014-05-11 16:18:55 +00:00
}
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()
2014-05-11 16:18:55 +00:00
}
// 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 (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
2014-05-11 16:18:55 +00:00
for {
if prev == BufSize {
log.Println(c, "input buffer size exceeded, kicking him")
break
}
n, err = c.conn.Read(buf[prev:])
2014-05-11 16:18:55 +00:00
if err != nil {
break
}
prev += n
CheckMore:
i = bytes.Index(buf[:prev], CRLF)
if i == -1 {
2014-05-11 16:18:55 +00:00
continue
}
sink <- ClientEvent{c, EventMsg, string(buf[:i])}
copy(buf, buf[i+2:prev])
prev -= (i + 2)
goto CheckMore
2014-05-11 16:18:55 +00:00
}
c.Close()
sink <- ClientEvent{c, EventDel, ""}
}
func (c *Client) MsgSender() {
for msg := range c.outBuf {
c.conn.Write(append([]byte(msg), CRLF...))
}
2014-05-11 16:18:55 +00:00
}
// Send message as is with CRLF appended.
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
2014-05-11 16:18:55 +00:00
}
// Send message from server. It has ": servername" prefix.
func (c *Client) Reply(text string) {
c.Msg(":" + *hostname + " " + text)
2014-05-11 16:18:55 +00:00
}
// Send server message, concatenating all provided text parts and
// prefix the last one with ":".
func (c *Client) ReplyParts(code string, text ...string) {
2014-05-11 16:18:55 +00:00
parts := []string{code}
for _, t := range text {
parts = append(parts, t)
}
parts[len(parts)-1] = ":" + parts[len(parts)-1]
c.Reply(strings.Join(parts, " "))
2014-05-11 16:18:55 +00:00
}
// Send nicknamed server message. After servername it always has target
// client's nickname. The last part is prefixed with ":".
func (c *Client) ReplyNicknamed(code string, text ...string) {
c.ReplyParts(code, append([]string{*c.nickname}, text...)...)
2014-05-11 16:18:55 +00:00
}
// Reply "461 not enough parameters" error for given command.
func (c *Client) ReplyNotEnoughParameters(command string) {
c.ReplyNicknamed("461", command, "Not enough parameters")
2014-05-11 16:18:55 +00:00
}
// Reply "403 no such channel" error for specified channel.
func (c *Client) ReplyNoChannel(channel string) {
c.ReplyNicknamed("403", channel, "No such channel")
2014-05-11 16:18:55 +00:00
}
2014-05-13 19:56:29 +00:00
func (c *Client) ReplyNoNickChan(channel string) {
c.ReplyNicknamed("401", channel, "No such nick/channel")
2014-05-13 19:56:29 +00:00
}