]> Cypherpunks.ru repositories - goircd.git/blob - daemon.go
add service && debian package
[goircd.git] / daemon.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2017 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, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package main
20
21 import (
22         "fmt"
23         "io/ioutil"
24         "log"
25         "net"
26         "regexp"
27         "sort"
28         "strings"
29         "sync"
30         "time"
31 )
32
33 const (
34         // Max deadline time of client's unresponsiveness
35         PingTimeout = time.Second * 180
36         // Max idle client's time before PING are sent
37         PingThreshold = time.Second * 90
38 )
39
40 var (
41         RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$")
42
43         clients    map[*Client]struct{} = make(map[*Client]struct{})
44         clientsM   sync.RWMutex
45         rooms      map[string]*Room = make(map[string]*Room)
46         roomsM     sync.RWMutex
47         roomsGroup sync.WaitGroup
48         roomSinks  map[*Room]chan ClientEvent = make(map[*Room]chan ClientEvent)
49 )
50
51 func SendLusers(client *Client) {
52         lusers := 0
53         clientsM.RLock()
54         for client := range clients {
55                 if client.registered {
56                         lusers++
57                 }
58         }
59         clientsM.RUnlock()
60         client.ReplyNicknamed("251", fmt.Sprintf("There are %d users and 0 invisible on 1 servers", lusers))
61 }
62
63 func SendMotd(client *Client) {
64         if motd == nil {
65                 client.ReplyNicknamed("422", "MOTD File is missing")
66                 return
67         }
68         motdText, err := ioutil.ReadFile(*motd)
69         if err != nil {
70                 log.Printf("Can not read motd file %s: %v", *motd, err)
71                 client.ReplyNicknamed("422", "Error reading MOTD File")
72                 return
73         }
74         client.ReplyNicknamed("375", "- "+*hostname+" Message of the day -")
75         for _, s := range strings.Split(strings.TrimSuffix(string(motdText), "\n"), "\n") {
76                 client.ReplyNicknamed("372", "- "+s)
77         }
78         client.ReplyNicknamed("376", "End of /MOTD command")
79 }
80
81 func SendWhois(client *Client, nicknames []string) {
82         var c *Client
83         var hostPort string
84         var err error
85         var subscriptions []string
86         var room *Room
87         var subscriber *Client
88         for _, nickname := range nicknames {
89                 nickname = strings.ToLower(nickname)
90                 clientsM.RLock()
91                 for c = range clients {
92                         if strings.ToLower(*c.nickname) == nickname {
93                                 clientsM.RUnlock()
94                                 goto Found
95                         }
96                 }
97                 clientsM.RUnlock()
98                 client.ReplyNoNickChan(nickname)
99                 continue
100         Found:
101                 hostPort, _, err = net.SplitHostPort(c.conn.RemoteAddr().String())
102                 if err != nil {
103                         log.Printf("Can't parse RemoteAddr %q: %v", hostPort, err)
104                         hostPort = "Unknown"
105                 }
106                 client.ReplyNicknamed("311", *c.nickname, *c.username, hostPort, "*", *c.realname)
107                 client.ReplyNicknamed("312", *c.nickname, *hostname, *hostname)
108                 if c.away != nil {
109                         client.ReplyNicknamed("301", *c.nickname, *c.away)
110                 }
111                 subscriptions = make([]string, 0)
112                 roomsM.RLock()
113                 for _, room = range rooms {
114                         for subscriber = range room.members {
115                                 if *subscriber.nickname == nickname {
116                                         subscriptions = append(subscriptions, *room.name)
117                                 }
118                         }
119                 }
120                 roomsM.RUnlock()
121                 sort.Strings(subscriptions)
122                 client.ReplyNicknamed("319", *c.nickname, strings.Join(subscriptions, " "))
123                 client.ReplyNicknamed("318", *c.nickname, "End of /WHOIS list")
124         }
125 }
126
127 func SendList(client *Client, cols []string) {
128         var rs []string
129         var r string
130         if (len(cols) > 1) && (cols[1] != "") {
131                 rs = strings.Split(strings.Split(cols[1], " ")[0], ",")
132         } else {
133                 rs = make([]string, 0)
134                 roomsM.RLock()
135                 for r = range rooms {
136                         rs = append(rs, r)
137                 }
138                 roomsM.RUnlock()
139         }
140         sort.Strings(rs)
141         var room *Room
142         var found bool
143         for _, r = range rs {
144                 roomsM.RLock()
145                 if room, found = rooms[r]; found {
146                         client.ReplyNicknamed(
147                                 "322",
148                                 r,
149                                 fmt.Sprintf("%d", len(room.members)),
150                                 *room.topic,
151                         )
152                 }
153                 roomsM.RUnlock()
154         }
155         client.ReplyNicknamed("323", "End of /LIST")
156 }
157
158 // Unregistered client workflow processor. Unregistered client:
159 // * is not PINGed
160 // * only QUIT, NICK and USER commands are processed
161 // * other commands are quietly ignored
162 // When client finishes NICK/USER workflow, then MOTD and LUSERS are send to him.
163 func ClientRegister(client *Client, cmd string, cols []string) {
164         switch cmd {
165         case "PASS":
166                 if len(cols) == 1 || len(cols[1]) < 1 {
167                         client.ReplyNotEnoughParameters("PASS")
168                         return
169                 }
170                 password := strings.TrimPrefix(cols[1], ":")
171                 client.password = &password
172         case "NICK":
173                 if len(cols) == 1 || len(cols[1]) < 1 {
174                         client.ReplyParts("431", "No nickname given")
175                         return
176                 }
177                 nickname := cols[1]
178                 // Compatibility with some clients prepending colons to nickname
179                 nickname = strings.TrimPrefix(nickname, ":")
180                 nickname = strings.ToLower(nickname)
181                 clientsM.RLock()
182                 for existingClient := range clients {
183                         if *existingClient.nickname == nickname {
184                                 clientsM.RUnlock()
185                                 client.ReplyParts("433", "*", nickname, "Nickname is already in use")
186                                 return
187                         }
188                 }
189                 clientsM.RUnlock()
190                 if !RENickname.MatchString(nickname) {
191                         client.ReplyParts("432", "*", cols[1], "Erroneous nickname")
192                         return
193                 }
194                 client.nickname = &nickname
195         case "USER":
196                 if len(cols) == 1 {
197                         client.ReplyNotEnoughParameters("USER")
198                         return
199                 }
200                 args := strings.SplitN(cols[1], " ", 4)
201                 if len(args) < 4 {
202                         client.ReplyNotEnoughParameters("USER")
203                         return
204                 }
205                 client.username = &args[0]
206                 realname := strings.TrimLeft(args[3], ":")
207                 client.realname = &realname
208         }
209         if *client.nickname != "*" && *client.username != "" {
210                 if passwords != nil && *passwords != "" {
211                         if client.password == nil {
212                                 client.ReplyParts("462", "You may not register")
213                                 client.Close()
214                                 return
215                         }
216                         contents, err := ioutil.ReadFile(*passwords)
217                         if err != nil {
218                                 log.Fatalf("Can no read passwords file %s: %s", *passwords, err)
219                                 return
220                         }
221                         for _, entry := range strings.Split(string(contents), "\n") {
222                                 if entry == "" {
223                                         continue
224                                 }
225                                 if lp := strings.Split(entry, ":"); lp[0] == *client.nickname && lp[1] != *client.password {
226                                         client.ReplyParts("462", "You may not register")
227                                         client.Close()
228                                         return
229                                 }
230                         }
231                 }
232                 client.registered = true
233                 client.ReplyNicknamed("001", "Hi, welcome to IRC")
234                 client.ReplyNicknamed("002", "Your host is "+*hostname+", running goircd "+version)
235                 client.ReplyNicknamed("003", "This server was created sometime")
236                 client.ReplyNicknamed("004", *hostname+" goircd o o")
237                 SendLusers(client)
238                 SendMotd(client)
239                 log.Println(client, "logged in")
240         }
241 }
242
243 // Register new room in Daemon. Create an object, events sink, save pointers
244 // to corresponding daemon's places and start room's processor goroutine.
245 func RoomRegister(name string) (*Room, chan ClientEvent) {
246         roomNew := NewRoom(name)
247         roomSink := make(chan ClientEvent)
248         roomsM.Lock()
249         rooms[name] = roomNew
250         roomSinks[roomNew] = roomSink
251         roomsM.Unlock()
252         go roomNew.Processor(roomSink)
253         roomsGroup.Add(1)
254         return roomNew, roomSink
255 }
256
257 func HandlerJoin(client *Client, cmd string) {
258         args := strings.Split(cmd, " ")
259         rs := strings.Split(args[0], ",")
260         var keys []string
261         if len(args) > 1 {
262                 keys = strings.Split(args[1], ",")
263         } else {
264                 keys = make([]string, 0)
265         }
266         var roomExisting *Room
267         var roomSink chan ClientEvent
268         var roomNew *Room
269         for n, room := range rs {
270                 if !RoomNameValid(room) {
271                         client.ReplyNoChannel(room)
272                         continue
273                 }
274                 var key string
275                 if (n < len(keys)) && (keys[n] != "") {
276                         key = keys[n]
277                 } else {
278                         key = ""
279                 }
280                 roomsM.RLock()
281                 for roomExisting, roomSink = range roomSinks {
282                         if room == *roomExisting.name {
283                                 roomsM.RUnlock()
284                                 if (*roomExisting.key != "") && (*roomExisting.key != key) {
285                                         goto Denied
286                                 }
287                                 roomSink <- ClientEvent{client, EventNew, ""}
288                                 goto Joined
289                         }
290                 }
291                 roomsM.RUnlock()
292                 roomNew, roomSink = RoomRegister(room)
293                 log.Println("Room", roomNew, "created")
294                 if key != "" {
295                         roomNew.key = &key
296                         roomNew.StateSave()
297                 }
298                 roomSink <- ClientEvent{client, EventNew, ""}
299                 continue
300         Denied:
301                 client.ReplyNicknamed("475", room, "Cannot join channel (+k) - bad key")
302         Joined:
303         }
304 }
305
306 func Processor(events chan ClientEvent, finished chan struct{}) {
307         var now time.Time
308         go func() {
309                 for {
310                         time.Sleep(10 * time.Second)
311                         events <- ClientEvent{eventType: EventTick}
312                 }
313         }()
314         for event := range events {
315                 now = time.Now()
316                 client := event.client
317                 switch event.eventType {
318                 case EventTick:
319                         clientsM.RLock()
320                         for c := range clients {
321                                 if c.recvTimestamp.Add(PingTimeout).Before(now) {
322                                         log.Println(c, "ping timeout")
323                                         c.Close()
324                                         continue
325                                 }
326                                 if c.sendTimestamp.Add(PingThreshold).Before(now) {
327                                         if c.registered {
328                                                 c.Msg("PING :" + *hostname)
329                                                 c.sendTimestamp = time.Now()
330                                         } else {
331                                                 log.Println(c, "ping timeout")
332                                                 c.Close()
333                                         }
334                                 }
335                         }
336                         clientsM.RUnlock()
337                         roomsM.Lock()
338                         for rn, r := range rooms {
339                                 if *statedir == "" && len(r.members) == 0 {
340                                         log.Println(rn, "emptied room")
341                                         delete(rooms, rn)
342                                         close(roomSinks[r])
343                                         delete(roomSinks, r)
344                                 }
345                         }
346                         roomsM.Unlock()
347                 case EventTerm:
348                         roomsM.RLock()
349                         for _, sink := range roomSinks {
350                                 sink <- ClientEvent{eventType: EventTerm}
351                         }
352                         roomsM.RUnlock()
353                         roomsGroup.Wait()
354                         close(finished)
355                         return
356                 case EventNew:
357                         clientsM.Lock()
358                         clients[client] = struct{}{}
359                         clientsM.Unlock()
360                 case EventDel:
361                         clientsM.Lock()
362                         delete(clients, client)
363                         clientsM.Unlock()
364                         roomsM.RLock()
365                         for _, roomSink := range roomSinks {
366                                 roomSink <- event
367                         }
368                         roomsM.RUnlock()
369                 case EventMsg:
370                         cols := strings.SplitN(event.text, " ", 2)
371                         cmd := strings.ToUpper(cols[0])
372                         if *verbose {
373                                 log.Println(client, "command", cmd)
374                         }
375                         if cmd == "QUIT" {
376                                 log.Println(client, "quit")
377                                 client.Close()
378                                 continue
379                         }
380                         if !client.registered {
381                                 ClientRegister(client, cmd, cols)
382                                 continue
383                         }
384                         if client != nil {
385                                 client.recvTimestamp = now
386                         }
387                         switch cmd {
388                         case "AWAY":
389                                 if len(cols) == 1 {
390                                         client.away = nil
391                                         client.ReplyNicknamed("305", "You are no longer marked as being away")
392                                         continue
393                                 }
394                                 msg := strings.TrimLeft(cols[1], ":")
395                                 client.away = &msg
396                                 client.ReplyNicknamed("306", "You have been marked as being away")
397                         case "JOIN":
398                                 if len(cols) == 1 || len(cols[1]) < 1 {
399                                         client.ReplyNotEnoughParameters("JOIN")
400                                         continue
401                                 }
402                                 HandlerJoin(client, cols[1])
403                         case "LIST":
404                                 SendList(client, cols)
405                         case "LUSERS":
406                                 SendLusers(client)
407                         case "MODE":
408                                 if len(cols) == 1 || len(cols[1]) < 1 {
409                                         client.ReplyNotEnoughParameters("MODE")
410                                         continue
411                                 }
412                                 cols = strings.SplitN(cols[1], " ", 2)
413                                 if cols[0] == *client.username {
414                                         if len(cols) == 1 {
415                                                 client.Msg("221 " + *client.nickname + " +")
416                                         } else {
417                                                 client.ReplyNicknamed("501", "Unknown MODE flag")
418                                         }
419                                         continue
420                                 }
421                                 room := cols[0]
422                                 roomsM.RLock()
423                                 r, found := rooms[room]
424                                 if !found {
425                                         client.ReplyNoChannel(room)
426                                         roomsM.RUnlock()
427                                         continue
428                                 }
429                                 if len(cols) == 1 {
430                                         roomSinks[r] <- ClientEvent{client, EventMode, ""}
431                                 } else {
432                                         roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
433                                 }
434                                 roomsM.RUnlock()
435                         case "MOTD":
436                                 SendMotd(client)
437                         case "PART":
438                                 if len(cols) == 1 || len(cols[1]) < 1 {
439                                         client.ReplyNotEnoughParameters("PART")
440                                         continue
441                                 }
442                                 rs := strings.Split(cols[1], " ")[0]
443                                 roomsM.RLock()
444                                 for _, room := range strings.Split(rs, ",") {
445                                         if r, found := rooms[room]; found {
446                                                 roomSinks[r] <- ClientEvent{client, EventDel, ""}
447                                         } else {
448                                                 client.ReplyNoChannel(room)
449                                         }
450                                 }
451                                 roomsM.RUnlock()
452                         case "PING":
453                                 if len(cols) == 1 {
454                                         client.ReplyNicknamed("409", "No origin specified")
455                                         continue
456                                 }
457                                 client.Reply(fmt.Sprintf("PONG %s :%s", *hostname, cols[1]))
458                         case "PONG":
459                                 continue
460                         case "NOTICE", "PRIVMSG":
461                                 if len(cols) == 1 {
462                                         client.ReplyNicknamed("411", "No recipient given ("+cmd+")")
463                                         continue
464                                 }
465                                 cols = strings.SplitN(cols[1], " ", 2)
466                                 if len(cols) == 1 {
467                                         client.ReplyNicknamed("412", "No text to send")
468                                         continue
469                                 }
470                                 msg := ""
471                                 target := strings.ToLower(cols[0])
472                                 clientsM.RLock()
473                                 for c := range clients {
474                                         if *c.nickname == target {
475                                                 msg = fmt.Sprintf(":%s %s %s %s", client, cmd, *c.nickname, cols[1])
476                                                 c.Msg(msg)
477                                                 if c.away != nil {
478                                                         client.ReplyNicknamed("301", *c.nickname, *c.away)
479                                                 }
480                                                 break
481                                         }
482                                 }
483                                 clientsM.RUnlock()
484                                 if msg != "" {
485                                         continue
486                                 }
487                                 roomsM.RLock()
488                                 if r, found := rooms[target]; found {
489                                         roomSinks[r] <- ClientEvent{
490                                                 client,
491                                                 EventMsg,
492                                                 cmd + " " + strings.TrimLeft(cols[1], ":"),
493                                         }
494                                 } else {
495                                         client.ReplyNoNickChan(target)
496                                 }
497                                 roomsM.RUnlock()
498                         case "TOPIC":
499                                 if len(cols) == 1 {
500                                         client.ReplyNotEnoughParameters("TOPIC")
501                                         continue
502                                 }
503                                 cols = strings.SplitN(cols[1], " ", 2)
504                                 roomsM.RLock()
505                                 r, found := rooms[cols[0]]
506                                 roomsM.RUnlock()
507                                 if !found {
508                                         client.ReplyNoChannel(cols[0])
509                                         continue
510                                 }
511                                 var change string
512                                 if len(cols) > 1 {
513                                         change = cols[1]
514                                 } else {
515                                         change = ""
516                                 }
517                                 roomsM.RLock()
518                                 roomSinks[r] <- ClientEvent{client, EventTopic, change}
519                                 roomsM.RUnlock()
520                         case "WHO":
521                                 if len(cols) == 1 || len(cols[1]) < 1 {
522                                         client.ReplyNotEnoughParameters("WHO")
523                                         continue
524                                 }
525                                 room := strings.Split(cols[1], " ")[0]
526                                 roomsM.RLock()
527                                 if r, found := rooms[room]; found {
528                                         roomSinks[r] <- ClientEvent{client, EventWho, ""}
529                                 } else {
530                                         client.ReplyNoChannel(room)
531                                 }
532                                 roomsM.RUnlock()
533                         case "WHOIS":
534                                 if len(cols) == 1 || len(cols[1]) < 1 {
535                                         client.ReplyNotEnoughParameters("WHOIS")
536                                         continue
537                                 }
538                                 cols := strings.Split(cols[1], " ")
539                                 nicknames := strings.Split(cols[len(cols)-1], ",")
540                                 SendWhois(client, nicknames)
541                         case "ISON":
542                                 if len(cols) == 1 || len(cols[1]) < 1 {
543                                         client.ReplyNotEnoughParameters("ISON")
544                                         continue
545                                 }
546                                 nicksKnown := make(map[string]struct{})
547                                 clientsM.RLock()
548                                 for c := range clients {
549                                         nicksKnown[*c.nickname] = struct{}{}
550                                 }
551                                 clientsM.RUnlock()
552                                 var nicksExists []string
553                                 for _, nickname := range strings.Split(cols[1], " ") {
554                                         if _, exists := nicksKnown[nickname]; exists {
555                                                 nicksExists = append(nicksExists, nickname)
556                                         }
557                                 }
558                                 client.ReplyNicknamed("303", strings.Join(nicksExists, " "))
559                         case "VERSION":
560                                 var debug string
561                                 if *verbose {
562                                         debug = "debug"
563                                 } else {
564                                         debug = ""
565                                 }
566                                 client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", version, debug, *hostname))
567                         default:
568                                 client.ReplyNicknamed("421", cmd, "Unknown command")
569                         }
570                 }
571         }
572 }