]> Cypherpunks.ru repositories - goircd.git/blob - client.go
950cbcaba02100a28fb0b9f994a41e026548ad56
[goircd.git] / client.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014 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 package main
19
20 import (
21         "bytes"
22         "log"
23         "net"
24         "strings"
25         "time"
26 )
27
28 const (
29         CRLF     = "\x0d\x0a"
30         BUF_SIZE = 1380
31 )
32
33 type Client struct {
34         hostname   string
35         conn       net.Conn
36         registered bool
37         ping_sent  bool
38         timestamp  time.Time
39         nickname   string
40         username   string
41         realname   string
42 }
43
44 func (client Client) String() string {
45         return client.nickname + "!" + client.username + "@" + client.conn.RemoteAddr().String()
46 }
47
48 func NewClient(hostname string, conn net.Conn) *Client {
49         return &Client{hostname: hostname, conn: conn, nickname: "*"}
50 }
51
52 // Client processor blockingly reads everything remote client sends,
53 // splits messages by CRLF and send them to Daemon gorouting for processing
54 // it futher. Also it can signalize that client is unavailable (disconnected).
55 func (client *Client) Processor(sink chan<- ClientEvent) {
56         var buf_net []byte
57         buf := make([]byte, 0)
58         log.Println(client, "New client")
59         sink <- ClientEvent{client, EVENT_NEW, ""}
60         for {
61                 buf_net = make([]byte, BUF_SIZE)
62                 _, err := client.conn.Read(buf_net)
63                 if err != nil {
64                         log.Println(client, "connection lost", err)
65                         sink <- ClientEvent{client, EVENT_DEL, ""}
66                         break
67                 }
68                 client.timestamp = time.Now()
69                 client.ping_sent = false
70                 buf_net = bytes.TrimRight(buf_net, "\x00")
71                 buf = append(buf, buf_net...)
72                 if !bytes.HasSuffix(buf, []byte(CRLF)) {
73                         continue
74                 }
75                 for _, msg := range bytes.Split(buf[:len(buf)-2], []byte(CRLF)) {
76                         if len(msg) > 0 {
77                                 sink <- ClientEvent{client, EVENT_MSG, string(msg)}
78                         }
79                 }
80                 buf = []byte{}
81         }
82 }
83
84 // Send message as is with CRLF appended.
85 func (client *Client) Msg(text string) {
86         client.conn.Write([]byte(text + CRLF))
87 }
88
89 // Send message from server. It has ": servername" prefix.
90 func (client *Client) Reply(text string) {
91         client.Msg(":" + client.hostname + " " + text)
92 }
93
94 // Send server message, concatenating all provided text parts and
95 // prefix the last one with ":".
96 func (client *Client) ReplyParts(code string, text ...string) {
97         parts := []string{code}
98         for _, t := range text {
99                 parts = append(parts, t)
100         }
101         parts[len(parts)-1] = ":" + parts[len(parts)-1]
102         client.Reply(strings.Join(parts, " "))
103 }
104
105 // Send nicknamed server message. After servername it always has target
106 // client's nickname. The last part is prefixed with ":".
107 func (client *Client) ReplyNicknamed(code string, text ...string) {
108         client.ReplyParts(code, append([]string{client.nickname}, text...)...)
109 }
110
111 // Reply "461 not enough parameters" error for given command.
112 func (client *Client) ReplyNotEnoughParameters(command string) {
113         client.ReplyNicknamed("461", command, "Not enough parameters")
114 }
115
116 // Reply "403 no such channel" error for specified channel.
117 func (client *Client) ReplyNoChannel(channel string) {
118         client.ReplyNicknamed("403", channel, "No such channel")
119 }
120
121 func (client *Client) ReplyNoNickChan(channel string) {
122         client.ReplyNicknamed("401", channel, "No such nick/channel")
123 }