]> Cypherpunks.ru repositories - goircd.git/blob - daemon.go
16f74c7aafad4b59c0b00175583ef85e7be950e6
[goircd.git] / daemon.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2023 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         "fmt"
22         "log"
23         "strings"
24         "time"
25 )
26
27 const (
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
32 )
33
34 func Processor(events chan ClientEvent, finished chan struct{}) {
35         var now time.Time
36         ticker := time.NewTicker(10 * time.Second)
37         go func() {
38                 for range ticker.C {
39                         events <- ClientEvent{eventType: EventTick}
40                 }
41         }()
42 EventsCycle:
43         for e := range events {
44                 now = time.Now()
45                 client := e.client
46                 switch e.eventType {
47                 case EventTick:
48                         clientsLock.RLock()
49                         for c := range clients {
50                                 if c.recvTimestamp.Add(PingTimeout).Before(now) {
51                                         if *verbose {
52                                                 log.Println(c, "ping timeout")
53                                         }
54                                         c.Close()
55                                         continue
56                                 }
57                                 if c.sendTimestamp.Add(PingThreshold).Before(now) {
58                                         if c.registered {
59                                                 c.Msg("PING :" + *hostname)
60                                                 c.sendTimestamp = time.Now()
61                                         } else {
62                                                 if *verbose {
63                                                         log.Println(c, "ping timeout")
64                                                 }
65                                                 c.Close()
66                                         }
67                                 }
68                         }
69                         clientsLock.RUnlock()
70                         roomsLock.Lock()
71                         for rn, r := range rooms {
72                                 if *statedir == "" && len(r.members) == 0 {
73                                         if *verbose {
74                                                 log.Println(rn, "emptied room")
75                                         }
76                                         delete(rooms, rn)
77                                         r.events <- ClientEvent{eventType: EventTerm}
78                                         close(r.events)
79                                 }
80                         }
81                         roomsLock.Unlock()
82                 case EventTerm:
83                         break EventsCycle
84                 case EventNew:
85                         clientsLock.Lock()
86                         clients[client] = struct{}{}
87                         clientsLock.Unlock()
88                 case EventDel:
89                         clientsLock.Lock()
90                         delete(clients, client)
91                         clientsLock.Unlock()
92                         roomsLock.RLock()
93                         for _, r := range rooms {
94                                 r.events <- e
95                         }
96                         roomsLock.RUnlock()
97                 case EventMsg:
98                         cols := strings.SplitN(e.text, " ", 2)
99                         cmd := strings.ToUpper(cols[0])
100                         if *verbose {
101                                 log.Println(client, "command", cmd)
102                         }
103                         if cmd == "QUIT" {
104                                 client.Close()
105                                 if *verbose {
106                                         log.Println(client, "quit")
107                                 }
108                                 continue
109                         }
110                         if !client.registered {
111                                 client.Register(cmd, cols)
112                                 continue
113                         }
114                         if client != nil {
115                                 client.recvTimestamp = now
116                         }
117                         switch cmd {
118                         case "AWAY":
119                                 if len(cols) == 1 {
120                                         client.away = ""
121                                         client.ReplyNicknamed("305", "You are no longer marked as being away")
122                                         continue
123                                 }
124                                 client.away = strings.TrimLeft(cols[1], ":")
125                                 client.ReplyNicknamed("306", "You have been marked as being away")
126                         case "JOIN":
127                                 if len(cols) == 1 || len(cols[1]) < 1 {
128                                         client.ReplyNotEnoughParameters("JOIN")
129                                         continue
130                                 }
131                                 client.Join(cols[1])
132                         case "LIST":
133                                 client.SendList(cols)
134                         case "LUSERS":
135                                 client.SendLusers()
136                         case "MODE":
137                                 if len(cols) == 1 || len(cols[1]) < 1 {
138                                         client.ReplyNotEnoughParameters("MODE")
139                                         continue
140                                 }
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")
145                                         continue
146                                 }
147                                 room := cols[0]
148                                 r, found := rooms[room]
149                                 if !found {
150                                         client.ReplyNoChannel(room)
151                                         continue
152                                 }
153                                 if len(cols) == 1 {
154                                         r.events <- ClientEvent{client, EventMode, ""}
155                                 } else {
156                                         r.events <- ClientEvent{client, EventMode, cols[1]}
157                                 }
158                         case "MOTD":
159                                 client.SendMotd()
160                         case "NAMES":
161                                 rs := make([]*Room, len(cols))
162                                 roomsLock.RLock()
163                                 if len(cols) == 0 {
164                                         for _, r := range rooms {
165                                                 rs = append(rs, r)
166                                         }
167                                 } else {
168                                         needed := make(map[string]struct{}, len(rs))
169                                         for _, r := range cols {
170                                                 needed[r] = struct{}{}
171                                         }
172                                         for rn, r := range rooms {
173                                                 if _, found := needed[rn]; found {
174                                                         rs = append(rs, r)
175                                                 }
176                                         }
177                                 }
178                                 roomsLock.RUnlock()
179                                 for _, r := range rs {
180                                         r.SendNames(client)
181                                 }
182                         case "PART":
183                                 if len(cols) == 1 || len(cols[1]) < 1 {
184                                         client.ReplyNotEnoughParameters("PART")
185                                         continue
186                                 }
187                                 rs := strings.Split(cols[1], " ")[0]
188                                 roomsLock.RLock()
189                                 for _, room := range strings.Split(rs, ",") {
190                                         if r, found := rooms[room]; found {
191                                                 r.events <- ClientEvent{client, EventDel, ""}
192                                         } else {
193                                                 client.ReplyNoChannel(room)
194                                         }
195                                 }
196                                 roomsLock.RUnlock()
197                         case "PING":
198                                 if len(cols) == 1 {
199                                         client.ReplyNicknamed("409", "No origin specified")
200                                         continue
201                                 }
202                                 client.Reply(fmt.Sprintf("PONG %s :%s", *hostname, cols[1]))
203                         case "PONG":
204                                 continue
205                         case "NOTICE", "PRIVMSG":
206                                 if len(cols) == 1 {
207                                         client.ReplyNicknamed("411", "No recipient given ("+cmd+")")
208                                         continue
209                                 }
210                                 cols = strings.SplitN(cols[1], " ", 2)
211                                 if len(cols) == 1 {
212                                         client.ReplyNicknamed("412", "No text to send")
213                                         continue
214                                 }
215                                 target := strings.ToLower(cols[0])
216                                 roomsLock.RLock()
217                                 if r, found := rooms[target]; found {
218                                         r.events <- ClientEvent{
219                                                 client,
220                                                 EventMsg,
221                                                 cmd + " " + strings.TrimLeft(cols[1], ":"),
222                                         }
223                                         roomsLock.RUnlock()
224                                         continue
225                                 }
226                                 roomsLock.RUnlock()
227                                 var msg string
228                                 clientsLock.RLock()
229                                 for c := range clients {
230                                         if c.nickname != target {
231                                                 continue
232                                         }
233                                         msg = fmt.Sprintf(":%s %s %s %s", client, cmd, c.nickname, cols[1])
234                                         c.Msg(msg)
235                                         if c.away != "" {
236                                                 client.ReplyNicknamed("301", c.nickname, c.away)
237                                         }
238                                         break
239                                 }
240                                 clientsLock.RUnlock()
241                                 if msg != "" {
242                                         continue
243                                 }
244                                 client.ReplyNoNickChan(target)
245                         case "TOPIC":
246                                 if len(cols) == 1 {
247                                         client.ReplyNotEnoughParameters("TOPIC")
248                                         continue
249                                 }
250                                 cols = strings.SplitN(cols[1], " ", 2)
251                                 r, found := rooms[cols[0]]
252                                 if !found {
253                                         client.ReplyNoChannel(cols[0])
254                                         continue
255                                 }
256                                 var change string
257                                 if len(cols) > 1 {
258                                         change = cols[1]
259                                 }
260                                 r.events <- ClientEvent{client, EventTopic, change}
261                         case "WHO":
262                                 if len(cols) == 1 || len(cols[1]) < 1 {
263                                         client.ReplyNotEnoughParameters("WHO")
264                                         continue
265                                 }
266                                 room := strings.Split(cols[1], " ")[0]
267                                 r, found := rooms[room]
268                                 if found {
269                                         r.events <- ClientEvent{client, EventWho, ""}
270                                 } else {
271                                         client.ReplyNoChannel(room)
272                                 }
273                         case "WHOIS":
274                                 if len(cols) == 1 || len(cols[1]) < 1 {
275                                         client.ReplyNotEnoughParameters("WHOIS")
276                                         continue
277                                 }
278                                 cols := strings.Split(cols[1], " ")
279                                 nicknames := strings.Split(cols[len(cols)-1], ",")
280                                 client.SendWhois(nicknames)
281                         case "ISON":
282                                 if len(cols) == 1 || len(cols[1]) < 1 {
283                                         client.ReplyNotEnoughParameters("ISON")
284                                         continue
285                                 }
286                                 nicknamesList := strings.Split(cols[1], " ")
287                                 nicknames := make(map[string]bool, len(nicknamesList))
288                                 for _, nickname := range nicknamesList {
289                                         nicknames[nickname] = false
290                                 }
291                                 clientsLock.RLock()
292                                 for c := range clients {
293                                         if _, exists := nicknames[c.nickname]; exists {
294                                                 nicknames[c.nickname] = true
295                                         }
296                                 }
297                                 clientsLock.RUnlock()
298                                 nicknamesList = nicknamesList[:0]
299                                 for n, exists := range nicknames {
300                                         if exists {
301                                                 nicknamesList = append(nicknamesList, n)
302                                         }
303                                 }
304                                 client.ReplyNicknamed("303", strings.Join(nicknamesList, " "))
305                         case "WALLOPS":
306                                 if len(cols) == 1 {
307                                         client.ReplyNotEnoughParameters("WALLOPS")
308                                         continue
309                                 }
310                                 cs := make([]*Client, 0, len(clients))
311                                 clientsLock.RLock()
312                                 for c := range clients {
313                                         if c != client {
314                                                 cs = append(cs, c)
315                                         }
316                                 }
317                                 clientsLock.RUnlock()
318                                 for _, c := range cs {
319                                         c.Msg(fmt.Sprintf(":%s NOTICE %s %s", client, c.nickname, cols[1]))
320                                 }
321                         case "VERSION":
322                                 var debug string
323                                 if *verbose {
324                                         debug = "debug"
325                                 }
326                                 client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", Version, debug, *hostname))
327                         default:
328                                 client.ReplyNicknamed("421", cmd, "Unknown command")
329                         }
330                 }
331         }
332         ticker.Stop()
333
334         // Notify all clients about shutdown
335         clientsLock.RLock()
336         for c := range clients {
337                 c.Msg(fmt.Sprintf(
338                         ":%s NOTICE %s %s", *hostname, c.nickname,
339                         ":Server is shutting down",
340                 ))
341                 c.Close()
342         }
343         clientsLock.RUnlock()
344
345         // Read their EventDel
346         go func() {
347                 for range events {
348                 }
349         }()
350
351         // Stop room processors
352         roomsLock.RLock()
353         for _, r := range rooms {
354                 r.events <- ClientEvent{eventType: EventTerm}
355         }
356         roomsLock.RUnlock()
357         roomsWG.Wait()
358
359         // Wait for either 5sec or all clients quitting
360         t := time.NewTimer(5 * time.Second)
361         clientsDone := make(chan struct{})
362         go func() {
363                 clientsWG.Wait()
364                 close(clientsDone)
365         }()
366         select {
367         case <-t.C:
368         case <-clientsDone:
369         }
370         if !t.Stop() {
371                 <-t.C
372         }
373         close(events)
374         close(finished)
375 }