]> Cypherpunks.ru repositories - goircd.git/blob - goircd.go
ddf81286a1ff07cf6daa982fc571a134f5e096a7
[goircd.git] / goircd.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2022 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         "crypto/tls"
22         "flag"
23         "io/ioutil"
24         "log"
25         "net"
26         "os"
27         "os/signal"
28         "path"
29         "path/filepath"
30         "strconv"
31         "strings"
32         "syscall"
33 )
34
35 const (
36         Version = "1.9.1"
37
38         StateTopicFilename = "topic"
39         StateKeyFilename   = "key"
40
41         EventNew   = iota
42         EventDel   = iota
43         EventMsg   = iota
44         EventTopic = iota
45         EventWho   = iota
46         EventMode  = iota
47         EventTerm  = iota
48         EventTick  = iota
49 )
50
51 type ClientEvent struct {
52         client    *Client
53         eventType int
54         text      string
55 }
56
57 type StateEvent struct {
58         where string
59         topic string
60         key   string
61 }
62
63 var (
64         hostname       = flag.String("hostname", "localhost", "hostname")
65         bind           = flag.String("bind", "[::1]:6667", "address to bind to")
66         cloak          = flag.String("cloak", "", "cloak user's host with the given hostname")
67         motd           = flag.String("motd", "", "path to MOTD file")
68         logdir         = flag.String("logdir", "", "absolute path to directory for logs")
69         statedir       = flag.String("statedir", "", "absolute path to directory for states")
70         passwords      = flag.String("passwd", "", "optional path to passwords file")
71         tlsBind        = flag.String("tlsbind", "", "TLS address to bind to")
72         tlsPEM         = flag.String("tlspem", "", "path to TLS certificat+key PEM file")
73         permStateDirS  = flag.String("perm-state-dir", "755", "state directory permissions")
74         permStateFileS = flag.String("perm-state-file", "600", "state files permissions")
75         permLogFileS   = flag.String("perm-log-file", "644", "log files permissions")
76         timestamped    = flag.Bool("timestamped", false, "enable timestamps on stderr messages")
77         verbose        = flag.Bool("verbose", false, "enable verbose logging")
78         debug          = flag.Bool("debug", false, "enable debug (traffic) logging")
79
80         permStateDir  os.FileMode
81         permStateFile os.FileMode
82         permLogFile   os.FileMode
83
84         stateSink chan StateEvent = make(chan StateEvent)
85 )
86
87 func permParse(s string) os.FileMode {
88         r, err := strconv.ParseUint(s, 8, 16)
89         if err != nil {
90                 log.Fatalln(err)
91         }
92         return os.FileMode(r)
93 }
94
95 func listenerLoop(ln net.Listener, events chan ClientEvent) {
96         for {
97                 conn, err := ln.Accept()
98                 if err != nil {
99                         log.Println("error during accept", err)
100                         continue
101                 }
102                 NewClient(conn, events)
103         }
104 }
105
106 func main() {
107         flag.Parse()
108         permStateDir = permParse(*permStateDirS)
109         permStateFile = permParse(*permStateFileS)
110         permLogFile = permParse(*permLogFileS)
111
112         if *timestamped {
113                 log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
114         } else {
115                 log.SetFlags(log.Lshortfile)
116         }
117         log.SetOutput(os.Stdout)
118
119         if *logdir == "" {
120                 // Dummy logger
121                 go func() {
122                         for range logSink {
123                         }
124                 }()
125         } else {
126                 if !path.IsAbs(*logdir) {
127                         log.Fatalln("need absolute path for logdir")
128                 }
129                 go Logger(*logdir, logSink)
130         }
131
132         if *statedir == "" {
133                 // Dummy statekeeper
134                 go func() {
135                         for range stateSink {
136                         }
137                 }()
138         } else {
139                 if !path.IsAbs(*statedir) {
140                         log.Fatalln("need absolute path for statedir")
141                 }
142                 states, err := filepath.Glob(path.Join(*statedir, "#*"))
143                 if err != nil {
144                         log.Fatalln("can not read statedir", err)
145                 }
146                 for _, state := range states {
147                         buf, err := ioutil.ReadFile(path.Join(state, StateTopicFilename))
148                         if err != nil {
149                                 log.Fatalf(
150                                         "can not read state %s/%s: %v",
151                                         state, StateTopicFilename, err,
152                                 )
153                         }
154                         room := RoomRegister(path.Base(state))
155                         room.topic = strings.TrimRight(string(buf), "\n")
156                         buf, err = ioutil.ReadFile(path.Join(state, StateKeyFilename))
157                         if err == nil {
158                                 room.key = strings.TrimRight(string(buf), "\n")
159                         } else {
160                                 if !os.IsNotExist(err) {
161                                         log.Fatalf(
162                                                 "can not read state %s/%s: %v",
163                                                 state, StateKeyFilename, err,
164                                         )
165                                 }
166                         }
167                         log.Println("loaded state for room:", room.name)
168                 }
169
170                 go func() {
171                         for event := range stateSink {
172                                 statePath := path.Join(*statedir, event.where)
173                                 if _, err := os.Stat(statePath); os.IsNotExist(err) {
174                                         if err := os.Mkdir(statePath, permStateDir); err != nil {
175                                                 log.Printf("can not create state %s: %v", statePath, err)
176                                                 continue
177                                         }
178                                 }
179
180                                 topicPath := path.Join(statePath, StateTopicFilename)
181                                 if err := ioutil.WriteFile(
182                                         topicPath,
183                                         []byte(event.topic+"\n"),
184                                         permStateFile,
185                                 ); err != nil {
186                                         log.Printf("can not write statefile %s: %v", topicPath, err)
187                                         continue
188                                 }
189
190                                 keyPath := path.Join(statePath, StateKeyFilename)
191                                 if err := ioutil.WriteFile(
192                                         keyPath,
193                                         []byte(event.key+"\n"),
194                                         permStateFile,
195                                 ); err != nil {
196                                         log.Printf("can not write statefile %s: %v", keyPath, err)
197                                 }
198                         }
199                 }()
200         }
201
202         events := make(chan ClientEvent)
203         if *bind != "" {
204                 ln, err := net.Listen("tcp", *bind)
205                 if err != nil {
206                         log.Fatalf("can not listen on %s: %v", *bind, err)
207                 }
208                 go listenerLoop(ln, events)
209         }
210         if *tlsBind != "" {
211                 cert, err := tls.LoadX509KeyPair(*tlsPEM, *tlsPEM)
212                 if err != nil {
213                         log.Fatalf("can not load TLS keys from %s: %s", *tlsPEM, err)
214                 }
215                 config := tls.Config{Certificates: []tls.Certificate{cert}}
216                 ln, err := tls.Listen("tcp", *tlsBind, &config)
217                 if err != nil {
218                         log.Fatalf("can not listen on %s: %v", *tlsBind, err)
219                 }
220                 go listenerLoop(ln, events)
221         }
222         log.Println("goircd", Version, "started")
223
224         needsShutdown := make(chan os.Signal, 0)
225         signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT)
226         go func() {
227                 <-needsShutdown
228                 events <- ClientEvent{eventType: EventTerm}
229                 log.Println("goircd shutting down")
230         }()
231
232         finished := make(chan struct{})
233         go Processor(events, finished)
234         <-finished
235 }