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