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