2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2020 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
28 // Max deadline time of client's unresponsiveness
29 PingTimeout = time.Second * 180
30 // Max idle client's time before PING are sent
31 PingThreshold = time.Second * 90
34 func Processor(events chan ClientEvent, finished chan struct{}) {
36 ticker := time.NewTicker(10 * time.Second)
39 events <- ClientEvent{eventType: EventTick}
43 for e := range events {
49 for c := range clients {
50 if c.recvTimestamp.Add(PingTimeout).Before(now) {
52 log.Println(c, "ping timeout")
57 if c.sendTimestamp.Add(PingThreshold).Before(now) {
59 c.Msg("PING :" + *hostname)
60 c.sendTimestamp = time.Now()
63 log.Println(c, "ping timeout")
71 for rn, r := range rooms {
72 if *statedir == "" && len(r.members) == 0 {
74 log.Println(rn, "emptied room")
85 clients[client] = struct{}{}
89 delete(clients, client)
92 for _, r := range rooms {
97 cols := strings.SplitN(e.text, " ", 2)
98 cmd := strings.ToUpper(cols[0])
100 log.Println(client, "command", cmd)
105 log.Println(client, "quit")
109 if !client.registered {
110 client.Register(cmd, cols)
114 client.recvTimestamp = now
120 client.ReplyNicknamed("305", "You are no longer marked as being away")
123 client.away = strings.TrimLeft(cols[1], ":")
124 client.ReplyNicknamed("306", "You have been marked as being away")
126 if len(cols) == 1 || len(cols[1]) < 1 {
127 client.ReplyNotEnoughParameters("JOIN")
132 client.SendList(cols)
136 if len(cols) == 1 || len(cols[1]) < 1 {
137 client.ReplyNotEnoughParameters("MODE")
140 cols = strings.SplitN(cols[1], " ", 2)
141 if cols[0] == client.username {
142 client.Msg("221 " + client.nickname + " +w")
143 // client.ReplyNicknamed("501", "Unknown MODE flag")
147 r, found := rooms[room]
149 client.ReplyNoChannel(room)
153 r.events <- ClientEvent{client, EventMode, ""}
155 r.events <- ClientEvent{client, EventMode, cols[1]}
160 rs := make([]*Room, len(cols))
163 for _, r := range rooms {
167 needed := make(map[string]struct{}, len(rs))
168 for _, r := range cols {
169 needed[r] = struct{}{}
171 for rn, r := range rooms {
172 if _, found := needed[rn]; found {
178 for _, r := range rs {
182 if len(cols) == 1 || len(cols[1]) < 1 {
183 client.ReplyNotEnoughParameters("PART")
186 rs := strings.Split(cols[1], " ")[0]
188 for _, room := range strings.Split(rs, ",") {
189 if r, found := rooms[room]; found {
190 r.events <- ClientEvent{client, EventDel, ""}
192 client.ReplyNoChannel(room)
198 client.ReplyNicknamed("409", "No origin specified")
201 client.Reply(fmt.Sprintf("PONG %s :%s", *hostname, cols[1]))
204 case "NOTICE", "PRIVMSG":
206 client.ReplyNicknamed("411", "No recipient given ("+cmd+")")
209 cols = strings.SplitN(cols[1], " ", 2)
211 client.ReplyNicknamed("412", "No text to send")
214 target := strings.ToLower(cols[0])
216 if r, found := rooms[target]; found {
217 r.events <- ClientEvent{
220 cmd + " " + strings.TrimLeft(cols[1], ":"),
228 for c := range clients {
229 if c.nickname != target {
232 msg = fmt.Sprintf(":%s %s %s %s", client, cmd, c.nickname, cols[1])
235 client.ReplyNicknamed("301", c.nickname, c.away)
239 clientsLock.RUnlock()
243 client.ReplyNoNickChan(target)
246 client.ReplyNotEnoughParameters("TOPIC")
249 cols = strings.SplitN(cols[1], " ", 2)
250 r, found := rooms[cols[0]]
252 client.ReplyNoChannel(cols[0])
259 r.events <- ClientEvent{client, EventTopic, change}
261 if len(cols) == 1 || len(cols[1]) < 1 {
262 client.ReplyNotEnoughParameters("WHO")
265 room := strings.Split(cols[1], " ")[0]
266 r, found := rooms[room]
268 r.events <- ClientEvent{client, EventWho, ""}
270 client.ReplyNoChannel(room)
273 if len(cols) == 1 || len(cols[1]) < 1 {
274 client.ReplyNotEnoughParameters("WHOIS")
277 cols := strings.Split(cols[1], " ")
278 nicknames := strings.Split(cols[len(cols)-1], ",")
279 client.SendWhois(nicknames)
281 if len(cols) == 1 || len(cols[1]) < 1 {
282 client.ReplyNotEnoughParameters("ISON")
285 nicknamesList := strings.Split(cols[1], " ")
286 nicknames := make(map[string]bool, len(nicknamesList))
287 for _, nickname := range nicknamesList {
288 nicknames[nickname] = false
291 for c := range clients {
292 if _, exists := nicknames[c.nickname]; exists {
293 nicknames[c.nickname] = true
296 clientsLock.RUnlock()
297 nicknamesList = nicknamesList[:0]
298 for n, exists := range nicknames {
300 nicknamesList = append(nicknamesList, n)
303 client.ReplyNicknamed("303", strings.Join(nicknamesList, " "))
306 client.ReplyNotEnoughParameters("WALLOPS")
309 cs := make([]*Client, 0, len(clients))
311 for c := range clients {
316 clientsLock.RUnlock()
317 for _, c := range cs {
318 c.Msg(fmt.Sprintf(":%s NOTICE %s %s", client, c.nickname, cols[1]))
325 client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", Version, debug, *hostname))
327 client.ReplyNicknamed("421", cmd, "Unknown command")
333 // Notify all clients about shutdown
335 for c := range clients {
337 ":%s NOTICE %s %s", *hostname, c.nickname,
338 ":Server is shutting down",
342 clientsLock.RUnlock()
344 // Read their EventDel
350 // Stop room processors
352 for _, r := range rooms {
353 r.events <- ClientEvent{eventType: EventTerm}
358 // Wait for either 5sec or all clients quitting
359 t := time.NewTimer(5 * time.Second)
360 clientsDone := make(chan struct{})