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