]> Cypherpunks.ru repositories - goircd.git/blob - client.go
Forbid any later GNU GPL versions autousage
[goircd.git] / client.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2018 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "bytes"
22         "log"
23         "net"
24         "strings"
25         "sync"
26         "time"
27 )
28
29 const (
30         BufSize   = 1500
31         MaxOutBuf = 1 << 12
32 )
33
34 var (
35         CRLF []byte = []byte{'\x0d', '\x0a'}
36 )
37
38 type Client struct {
39         conn          net.Conn
40         registered    bool
41         nickname      *string
42         username      *string
43         realname      *string
44         password      *string
45         away          *string
46         recvTimestamp time.Time
47         sendTimestamp time.Time
48         outBuf        chan *string
49         alive         bool
50         sync.Mutex
51 }
52
53 func (c *Client) Host() string {
54         addr := c.conn.RemoteAddr().String()
55         if host, _, err := net.SplitHostPort(addr); err == nil {
56                 addr = host
57         }
58         if domains, err := net.LookupAddr(addr); err == nil {
59                 addr = strings.TrimSuffix(domains[0], ".")
60         }
61         return addr
62 }
63
64 func (c *Client) String() string {
65         return *c.nickname + "!" + *c.username + "@" + c.Host()
66 }
67
68 func NewClient(conn net.Conn) *Client {
69         nickname := "*"
70         username := ""
71         c := Client{
72                 conn:          conn,
73                 nickname:      &nickname,
74                 username:      &username,
75                 recvTimestamp: time.Now(),
76                 sendTimestamp: time.Now(),
77                 alive:         true,
78                 outBuf:        make(chan *string, MaxOutBuf),
79         }
80         go c.MsgSender()
81         return &c
82 }
83
84 func (c *Client) SetDead() {
85         c.outBuf <- nil
86         c.alive = false
87 }
88
89 func (c *Client) Close() {
90         c.Lock()
91         if c.alive {
92                 c.SetDead()
93         }
94         c.Unlock()
95 }
96
97 // Client processor blockingly reads everything remote client sends,
98 // splits messages by CRLF and send them to Daemon gorouting for processing
99 // it futher. Also it can signalize that client is unavailable (disconnected).
100 func (c *Client) Processor(sink chan ClientEvent) {
101         sink <- ClientEvent{c, EventNew, ""}
102         log.Println(c, "New client")
103         buf := make([]byte, BufSize*2)
104         var n int
105         var prev int
106         var i int
107         var err error
108         for {
109                 if prev == BufSize {
110                         log.Println(c, "input buffer size exceeded, kicking him")
111                         break
112                 }
113                 n, err = c.conn.Read(buf[prev:])
114                 if err != nil {
115                         break
116                 }
117                 prev += n
118         CheckMore:
119                 i = bytes.Index(buf[:prev], CRLF)
120                 if i == -1 {
121                         continue
122                 }
123                 sink <- ClientEvent{c, EventMsg, string(buf[:i])}
124                 copy(buf, buf[i+2:prev])
125                 prev -= (i + 2)
126                 goto CheckMore
127         }
128         c.Close()
129         sink <- ClientEvent{c, EventDel, ""}
130 }
131
132 func (c *Client) MsgSender() {
133         for msg := range c.outBuf {
134                 if msg == nil {
135                         c.conn.Close()
136                         break
137                 }
138                 c.conn.Write(append([]byte(*msg), CRLF...))
139         }
140 }
141
142 // Send message as is with CRLF appended.
143 func (c *Client) Msg(text string) {
144         c.Lock()
145         defer c.Unlock()
146         if !c.alive {
147                 return
148         }
149         if len(c.outBuf) == MaxOutBuf {
150                 log.Println(c, "output buffer size exceeded, kicking him")
151                 if c.alive {
152                         c.SetDead()
153                 }
154                 return
155         }
156         c.outBuf <- &text
157 }
158
159 // Send message from server. It has ": servername" prefix.
160 func (c *Client) Reply(text string) {
161         c.Msg(":" + *hostname + " " + text)
162 }
163
164 // Send server message, concatenating all provided text parts and
165 // prefix the last one with ":".
166 func (c *Client) ReplyParts(code string, text ...string) {
167         parts := []string{code}
168         for _, t := range text {
169                 parts = append(parts, t)
170         }
171         parts[len(parts)-1] = ":" + parts[len(parts)-1]
172         c.Reply(strings.Join(parts, " "))
173 }
174
175 // Send nicknamed server message. After servername it always has target
176 // client's nickname. The last part is prefixed with ":".
177 func (c *Client) ReplyNicknamed(code string, text ...string) {
178         c.ReplyParts(code, append([]string{*c.nickname}, text...)...)
179 }
180
181 // Reply "461 not enough parameters" error for given command.
182 func (c *Client) ReplyNotEnoughParameters(command string) {
183         c.ReplyNicknamed("461", command, "Not enough parameters")
184 }
185
186 // Reply "403 no such channel" error for specified channel.
187 func (c *Client) ReplyNoChannel(channel string) {
188         c.ReplyNicknamed("403", channel, "No such channel")
189 }
190
191 func (c *Client) ReplyNoNickChan(channel string) {
192         c.ReplyNicknamed("401", channel, "No such nick/channel")
193 }