2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2022 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")
77 r.events <- ClientEvent{eventType: EventTerm}
86 clients[client] = struct{}{}
90 delete(clients, client)
93 for _, r := range rooms {
98 cols := strings.SplitN(e.text, " ", 2)
99 cmd := strings.ToUpper(cols[0])
101 log.Println(client, "command", cmd)
106 log.Println(client, "quit")
110 if !client.registered {
111 client.Register(cmd, cols)
115 client.recvTimestamp = now
121 client.ReplyNicknamed("305", "You are no longer marked as being away")
124 client.away = strings.TrimLeft(cols[1], ":")
125 client.ReplyNicknamed("306", "You have been marked as being away")
127 if len(cols) == 1 || len(cols[1]) < 1 {
128 client.ReplyNotEnoughParameters("JOIN")
133 client.SendList(cols)
137 if len(cols) == 1 || len(cols[1]) < 1 {
138 client.ReplyNotEnoughParameters("MODE")
141 cols = strings.SplitN(cols[1], " ", 2)
142 if cols[0] == client.username {
143 client.Msg("221 " + client.nickname + " +w")
144 // client.ReplyNicknamed("501", "Unknown MODE flag")
148 r, found := rooms[room]
150 client.ReplyNoChannel(room)
154 r.events <- ClientEvent{client, EventMode, ""}
156 r.events <- ClientEvent{client, EventMode, cols[1]}
161 rs := make([]*Room, len(cols))
164 for _, r := range rooms {
168 needed := make(map[string]struct{}, len(rs))
169 for _, r := range cols {
170 needed[r] = struct{}{}
172 for rn, r := range rooms {
173 if _, found := needed[rn]; found {
179 for _, r := range rs {
183 if len(cols) == 1 || len(cols[1]) < 1 {
184 client.ReplyNotEnoughParameters("PART")
187 rs := strings.Split(cols[1], " ")[0]
189 for _, room := range strings.Split(rs, ",") {
190 if r, found := rooms[room]; found {
191 r.events <- ClientEvent{client, EventDel, ""}
193 client.ReplyNoChannel(room)
199 client.ReplyNicknamed("409", "No origin specified")
202 client.Reply(fmt.Sprintf("PONG %s :%s", *hostname, cols[1]))
205 case "NOTICE", "PRIVMSG":
207 client.ReplyNicknamed("411", "No recipient given ("+cmd+")")
210 cols = strings.SplitN(cols[1], " ", 2)
212 client.ReplyNicknamed("412", "No text to send")
215 target := strings.ToLower(cols[0])
217 if r, found := rooms[target]; found {
218 r.events <- ClientEvent{
221 cmd + " " + strings.TrimLeft(cols[1], ":"),
229 for c := range clients {
230 if c.nickname != target {
233 msg = fmt.Sprintf(":%s %s %s %s", client, cmd, c.nickname, cols[1])
236 client.ReplyNicknamed("301", c.nickname, c.away)
240 clientsLock.RUnlock()
244 client.ReplyNoNickChan(target)
247 client.ReplyNotEnoughParameters("TOPIC")
250 cols = strings.SplitN(cols[1], " ", 2)
251 r, found := rooms[cols[0]]
253 client.ReplyNoChannel(cols[0])
260 r.events <- ClientEvent{client, EventTopic, change}
262 if len(cols) == 1 || len(cols[1]) < 1 {
263 client.ReplyNotEnoughParameters("WHO")
266 room := strings.Split(cols[1], " ")[0]
267 r, found := rooms[room]
269 r.events <- ClientEvent{client, EventWho, ""}
271 client.ReplyNoChannel(room)
274 if len(cols) == 1 || len(cols[1]) < 1 {
275 client.ReplyNotEnoughParameters("WHOIS")
278 cols := strings.Split(cols[1], " ")
279 nicknames := strings.Split(cols[len(cols)-1], ",")
280 client.SendWhois(nicknames)
282 if len(cols) == 1 || len(cols[1]) < 1 {
283 client.ReplyNotEnoughParameters("ISON")
286 nicknamesList := strings.Split(cols[1], " ")
287 nicknames := make(map[string]bool, len(nicknamesList))
288 for _, nickname := range nicknamesList {
289 nicknames[nickname] = false
292 for c := range clients {
293 if _, exists := nicknames[c.nickname]; exists {
294 nicknames[c.nickname] = true
297 clientsLock.RUnlock()
298 nicknamesList = nicknamesList[:0]
299 for n, exists := range nicknames {
301 nicknamesList = append(nicknamesList, n)
304 client.ReplyNicknamed("303", strings.Join(nicknamesList, " "))
307 client.ReplyNotEnoughParameters("WALLOPS")
310 cs := make([]*Client, 0, len(clients))
312 for c := range clients {
317 clientsLock.RUnlock()
318 for _, c := range cs {
319 c.Msg(fmt.Sprintf(":%s NOTICE %s %s", client, c.nickname, cols[1]))
326 client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", Version, debug, *hostname))
328 client.ReplyNicknamed("421", cmd, "Unknown command")
334 // Notify all clients about shutdown
336 for c := range clients {
338 ":%s NOTICE %s %s", *hostname, c.nickname,
339 ":Server is shutting down",
343 clientsLock.RUnlock()
345 // Read their EventDel
351 // Stop room processors
353 for _, r := range rooms {
354 r.events <- ClientEvent{eventType: EventTerm}
359 // Wait for either 5sec or all clients quitting
360 t := time.NewTimer(5 * time.Second)
361 clientsDone := make(chan struct{})