source: code/trunk/service.go@ 662

Last change on this file since 662 was 652, checked in by contact, 4 years ago

Add context args to Database interface

This is a mecanical change, which just lifts up the context.TODO()
calls from inside the DB implementations to the callers.

Future work involves properly wiring up the contexts when it makes
sense.

File size: 25.2 KB
Line 
1package soju
2
3import (
4 "context"
5 "crypto/sha1"
6 "crypto/sha256"
7 "crypto/sha512"
8 "encoding/hex"
9 "errors"
10 "flag"
11 "fmt"
12 "io/ioutil"
13 "sort"
14 "strconv"
15 "strings"
16 "time"
17 "unicode"
18
19 "golang.org/x/crypto/bcrypt"
20 "gopkg.in/irc.v3"
21)
22
23const serviceNick = "BouncerServ"
24const serviceNickCM = "bouncerserv"
25const serviceRealname = "soju bouncer service"
26
27// maxRSABits is the maximum number of RSA key bits used when generating a new
28// private key.
29const maxRSABits = 8192
30
31var servicePrefix = &irc.Prefix{
32 Name: serviceNick,
33 User: serviceNick,
34 Host: serviceNick,
35}
36
37type serviceCommandSet map[string]*serviceCommand
38
39type serviceCommand struct {
40 usage string
41 desc string
42 handle func(dc *downstreamConn, params []string) error
43 children serviceCommandSet
44 admin bool
45}
46
47func sendServiceNOTICE(dc *downstreamConn, text string) {
48 dc.SendMessage(&irc.Message{
49 Prefix: servicePrefix,
50 Command: "NOTICE",
51 Params: []string{dc.nick, text},
52 })
53}
54
55func sendServicePRIVMSG(dc *downstreamConn, text string) {
56 dc.SendMessage(&irc.Message{
57 Prefix: servicePrefix,
58 Command: "PRIVMSG",
59 Params: []string{dc.nick, text},
60 })
61}
62
63func splitWords(s string) ([]string, error) {
64 var words []string
65 var lastWord strings.Builder
66 escape := false
67 prev := ' '
68 wordDelim := ' '
69
70 for _, r := range s {
71 if escape {
72 // last char was a backslash, write the byte as-is.
73 lastWord.WriteRune(r)
74 escape = false
75 } else if r == '\\' {
76 escape = true
77 } else if wordDelim == ' ' && unicode.IsSpace(r) {
78 // end of last word
79 if !unicode.IsSpace(prev) {
80 words = append(words, lastWord.String())
81 lastWord.Reset()
82 }
83 } else if r == wordDelim {
84 // wordDelim is either " or ', switch back to
85 // space-delimited words.
86 wordDelim = ' '
87 } else if r == '"' || r == '\'' {
88 if wordDelim == ' ' {
89 // start of (double-)quoted word
90 wordDelim = r
91 } else {
92 // either wordDelim is " and r is ' or vice-versa
93 lastWord.WriteRune(r)
94 }
95 } else {
96 lastWord.WriteRune(r)
97 }
98
99 prev = r
100 }
101
102 if !unicode.IsSpace(prev) {
103 words = append(words, lastWord.String())
104 }
105
106 if wordDelim != ' ' {
107 return nil, fmt.Errorf("unterminated quoted string")
108 }
109 if escape {
110 return nil, fmt.Errorf("unterminated backslash sequence")
111 }
112
113 return words, nil
114}
115
116func handleServicePRIVMSG(dc *downstreamConn, text string) {
117 words, err := splitWords(text)
118 if err != nil {
119 sendServicePRIVMSG(dc, fmt.Sprintf(`error: failed to parse command: %v`, err))
120 return
121 }
122
123 cmd, params, err := serviceCommands.Get(words)
124 if err != nil {
125 sendServicePRIVMSG(dc, fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
126 return
127 }
128 if cmd.admin && !dc.user.Admin {
129 sendServicePRIVMSG(dc, "error: you must be an admin to use this command")
130 return
131 }
132
133 if cmd.handle == nil {
134 if len(cmd.children) > 0 {
135 var l []string
136 appendServiceCommandSetHelp(cmd.children, words, dc.user.Admin, &l)
137 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
138 } else {
139 // Pretend the command does not exist if it has neither children nor handler.
140 // This is obviously a bug but it is better to not die anyway.
141 dc.logger.Printf("command without handler and subcommands invoked:", words[0])
142 sendServicePRIVMSG(dc, fmt.Sprintf("command %q not found", words[0]))
143 }
144 return
145 }
146
147 if err := cmd.handle(dc, params); err != nil {
148 sendServicePRIVMSG(dc, fmt.Sprintf("error: %v", err))
149 }
150}
151
152func (cmds serviceCommandSet) Get(params []string) (*serviceCommand, []string, error) {
153 if len(params) == 0 {
154 return nil, nil, fmt.Errorf("no command specified")
155 }
156
157 name := params[0]
158 params = params[1:]
159
160 cmd, ok := cmds[name]
161 if !ok {
162 for k := range cmds {
163 if !strings.HasPrefix(k, name) {
164 continue
165 }
166 if cmd != nil {
167 return nil, params, fmt.Errorf("command %q is ambiguous", name)
168 }
169 cmd = cmds[k]
170 }
171 }
172 if cmd == nil {
173 return nil, params, fmt.Errorf("command %q not found", name)
174 }
175
176 if len(params) == 0 || len(cmd.children) == 0 {
177 return cmd, params, nil
178 }
179 return cmd.children.Get(params)
180}
181
182func (cmds serviceCommandSet) Names() []string {
183 l := make([]string, 0, len(cmds))
184 for name := range cmds {
185 l = append(l, name)
186 }
187 sort.Strings(l)
188 return l
189}
190
191var serviceCommands serviceCommandSet
192
193func init() {
194 serviceCommands = serviceCommandSet{
195 "help": {
196 usage: "[command]",
197 desc: "print help message",
198 handle: handleServiceHelp,
199 },
200 "network": {
201 children: serviceCommandSet{
202 "create": {
203 usage: "-addr <addr> [-name name] [-username username] [-pass pass] [-realname realname] [-nick nick] [-enabled enabled] [-connect-command command]...",
204 desc: "add a new network",
205 handle: handleServiceNetworkCreate,
206 },
207 "status": {
208 desc: "show a list of saved networks and their current status",
209 handle: handleServiceNetworkStatus,
210 },
211 "update": {
212 usage: "<name> [-addr addr] [-name name] [-username username] [-pass pass] [-realname realname] [-nick nick] [-enabled enabled] [-connect-command command]...",
213 desc: "update a network",
214 handle: handleServiceNetworkUpdate,
215 },
216 "delete": {
217 usage: "<name>",
218 desc: "delete a network",
219 handle: handleServiceNetworkDelete,
220 },
221 "quote": {
222 usage: "<name> <command>",
223 desc: "send a raw line to a network",
224 handle: handleServiceNetworkQuote,
225 },
226 },
227 },
228 "certfp": {
229 children: serviceCommandSet{
230 "generate": {
231 usage: "[-key-type rsa|ecdsa|ed25519] [-bits N] <network name>",
232 desc: "generate a new self-signed certificate, defaults to using RSA-3072 key",
233 handle: handleServiceCertFPGenerate,
234 },
235 "fingerprint": {
236 usage: "<network name>",
237 desc: "show fingerprints of certificate associated with the network",
238 handle: handleServiceCertFPFingerprints,
239 },
240 },
241 },
242 "sasl": {
243 children: serviceCommandSet{
244 "set-plain": {
245 usage: "<network name> <username> <password>",
246 desc: "set SASL PLAIN credentials",
247 handle: handleServiceSASLSetPlain,
248 },
249 "reset": {
250 usage: "<network name>",
251 desc: "disable SASL authentication and remove stored credentials",
252 handle: handleServiceSASLReset,
253 },
254 },
255 },
256 "user": {
257 children: serviceCommandSet{
258 "create": {
259 usage: "-username <username> -password <password> [-realname <realname>] [-admin]",
260 desc: "create a new soju user",
261 handle: handleUserCreate,
262 admin: true,
263 },
264 "update": {
265 usage: "[-password <password>] [-realname <realname>]",
266 desc: "update the current user",
267 handle: handleUserUpdate,
268 },
269 "delete": {
270 usage: "<username>",
271 desc: "delete a user",
272 handle: handleUserDelete,
273 admin: true,
274 },
275 },
276 },
277 "channel": {
278 children: serviceCommandSet{
279 "status": {
280 usage: "[-network name]",
281 desc: "show a list of saved channels and their current status",
282 handle: handleServiceChannelStatus,
283 },
284 "update": {
285 usage: "<name> [-relay-detached <default|none|highlight|message>] [-reattach-on <default|none|highlight|message>] [-detach-after <duration>] [-detach-on <default|none|highlight|message>]",
286 desc: "update a channel",
287 handle: handleServiceChannelUpdate,
288 },
289 },
290 },
291 "server": {
292 children: serviceCommandSet{
293 "status": {
294 desc: "show server statistics",
295 handle: handleServiceServerStatus,
296 admin: true,
297 },
298 "notice": {
299 desc: "broadcast a notice to all connected bouncer users",
300 handle: handleServiceServerNotice,
301 admin: true,
302 },
303 },
304 admin: true,
305 },
306 }
307}
308
309func appendServiceCommandSetHelp(cmds serviceCommandSet, prefix []string, admin bool, l *[]string) {
310 for _, name := range cmds.Names() {
311 cmd := cmds[name]
312 if cmd.admin && !admin {
313 continue
314 }
315 words := append(prefix, name)
316 if len(cmd.children) == 0 {
317 s := strings.Join(words, " ")
318 *l = append(*l, s)
319 } else {
320 appendServiceCommandSetHelp(cmd.children, words, admin, l)
321 }
322 }
323}
324
325func handleServiceHelp(dc *downstreamConn, params []string) error {
326 if len(params) > 0 {
327 cmd, rest, err := serviceCommands.Get(params)
328 if err != nil {
329 return err
330 }
331 words := params[:len(params)-len(rest)]
332
333 if len(cmd.children) > 0 {
334 var l []string
335 appendServiceCommandSetHelp(cmd.children, words, dc.user.Admin, &l)
336 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
337 } else {
338 text := strings.Join(words, " ")
339 if cmd.usage != "" {
340 text += " " + cmd.usage
341 }
342 text += ": " + cmd.desc
343
344 sendServicePRIVMSG(dc, text)
345 }
346 } else {
347 var l []string
348 appendServiceCommandSetHelp(serviceCommands, nil, dc.user.Admin, &l)
349 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
350 }
351 return nil
352}
353
354func newFlagSet() *flag.FlagSet {
355 fs := flag.NewFlagSet("", flag.ContinueOnError)
356 fs.SetOutput(ioutil.Discard)
357 return fs
358}
359
360type stringSliceFlag []string
361
362func (v *stringSliceFlag) String() string {
363 return fmt.Sprint([]string(*v))
364}
365
366func (v *stringSliceFlag) Set(s string) error {
367 *v = append(*v, s)
368 return nil
369}
370
371// stringPtrFlag is a flag value populating a string pointer. This allows to
372// disambiguate between a flag that hasn't been set and a flag that has been
373// set to an empty string.
374type stringPtrFlag struct {
375 ptr **string
376}
377
378func (f stringPtrFlag) String() string {
379 if f.ptr == nil || *f.ptr == nil {
380 return ""
381 }
382 return **f.ptr
383}
384
385func (f stringPtrFlag) Set(s string) error {
386 *f.ptr = &s
387 return nil
388}
389
390type boolPtrFlag struct {
391 ptr **bool
392}
393
394func (f boolPtrFlag) String() string {
395 if f.ptr == nil || *f.ptr == nil {
396 return "<nil>"
397 }
398 return strconv.FormatBool(**f.ptr)
399}
400
401func (f boolPtrFlag) Set(s string) error {
402 v, err := strconv.ParseBool(s)
403 if err != nil {
404 return err
405 }
406 *f.ptr = &v
407 return nil
408}
409
410type networkFlagSet struct {
411 *flag.FlagSet
412 Addr, Name, Nick, Username, Pass, Realname *string
413 Enabled *bool
414 ConnectCommands []string
415}
416
417func newNetworkFlagSet() *networkFlagSet {
418 fs := &networkFlagSet{FlagSet: newFlagSet()}
419 fs.Var(stringPtrFlag{&fs.Addr}, "addr", "")
420 fs.Var(stringPtrFlag{&fs.Name}, "name", "")
421 fs.Var(stringPtrFlag{&fs.Nick}, "nick", "")
422 fs.Var(stringPtrFlag{&fs.Username}, "username", "")
423 fs.Var(stringPtrFlag{&fs.Pass}, "pass", "")
424 fs.Var(stringPtrFlag{&fs.Realname}, "realname", "")
425 fs.Var(boolPtrFlag{&fs.Enabled}, "enabled", "")
426 fs.Var((*stringSliceFlag)(&fs.ConnectCommands), "connect-command", "")
427 return fs
428}
429
430func (fs *networkFlagSet) update(network *Network) error {
431 if fs.Addr != nil {
432 if addrParts := strings.SplitN(*fs.Addr, "://", 2); len(addrParts) == 2 {
433 scheme := addrParts[0]
434 switch scheme {
435 case "ircs", "irc+insecure", "unix":
436 default:
437 return fmt.Errorf("unknown scheme %q (supported schemes: ircs, irc+insecure, unix)", scheme)
438 }
439 }
440 network.Addr = *fs.Addr
441 }
442 if fs.Name != nil {
443 network.Name = *fs.Name
444 }
445 if fs.Nick != nil {
446 network.Nick = *fs.Nick
447 }
448 if fs.Username != nil {
449 network.Username = *fs.Username
450 }
451 if fs.Pass != nil {
452 network.Pass = *fs.Pass
453 }
454 if fs.Realname != nil {
455 network.Realname = *fs.Realname
456 }
457 if fs.Enabled != nil {
458 network.Enabled = *fs.Enabled
459 }
460 if fs.ConnectCommands != nil {
461 if len(fs.ConnectCommands) == 1 && fs.ConnectCommands[0] == "" {
462 network.ConnectCommands = nil
463 } else {
464 for _, command := range fs.ConnectCommands {
465 _, err := irc.ParseMessage(command)
466 if err != nil {
467 return fmt.Errorf("flag -connect-command must be a valid raw irc command string: %q: %v", command, err)
468 }
469 }
470 network.ConnectCommands = fs.ConnectCommands
471 }
472 }
473 return nil
474}
475
476func handleServiceNetworkCreate(dc *downstreamConn, params []string) error {
477 fs := newNetworkFlagSet()
478 if err := fs.Parse(params); err != nil {
479 return err
480 }
481 if fs.Addr == nil {
482 return fmt.Errorf("flag -addr is required")
483 }
484
485 record := &Network{
486 Addr: *fs.Addr,
487 Nick: dc.nick,
488 Enabled: true,
489 }
490 if err := fs.update(record); err != nil {
491 return err
492 }
493
494 network, err := dc.user.createNetwork(record)
495 if err != nil {
496 return fmt.Errorf("could not create network: %v", err)
497 }
498
499 sendServicePRIVMSG(dc, fmt.Sprintf("created network %q", network.GetName()))
500 return nil
501}
502
503func handleServiceNetworkStatus(dc *downstreamConn, params []string) error {
504 n := 0
505 dc.user.forEachNetwork(func(net *network) {
506 var statuses []string
507 var details string
508 if uc := net.conn; uc != nil {
509 if dc.nick != uc.nick {
510 statuses = append(statuses, "connected as "+uc.nick)
511 } else {
512 statuses = append(statuses, "connected")
513 }
514 details = fmt.Sprintf("%v channels", uc.channels.Len())
515 } else if !net.Enabled {
516 statuses = append(statuses, "disabled")
517 } else {
518 statuses = append(statuses, "disconnected")
519 if net.lastError != nil {
520 details = net.lastError.Error()
521 }
522 }
523
524 if net == dc.network {
525 statuses = append(statuses, "current")
526 }
527
528 name := net.GetName()
529 if name != net.Addr {
530 name = fmt.Sprintf("%v (%v)", name, net.Addr)
531 }
532
533 s := fmt.Sprintf("%v [%v]", name, strings.Join(statuses, ", "))
534 if details != "" {
535 s += ": " + details
536 }
537 sendServicePRIVMSG(dc, s)
538
539 n++
540 })
541
542 if n == 0 {
543 sendServicePRIVMSG(dc, `No network configured, add one with "network create".`)
544 }
545
546 return nil
547}
548
549func handleServiceNetworkUpdate(dc *downstreamConn, params []string) error {
550 if len(params) < 1 {
551 return fmt.Errorf("expected at least one argument")
552 }
553
554 fs := newNetworkFlagSet()
555 if err := fs.Parse(params[1:]); err != nil {
556 return err
557 }
558
559 net := dc.user.getNetwork(params[0])
560 if net == nil {
561 return fmt.Errorf("unknown network %q", params[0])
562 }
563
564 record := net.Network // copy network record because we'll mutate it
565 if err := fs.update(&record); err != nil {
566 return err
567 }
568
569 network, err := dc.user.updateNetwork(&record)
570 if err != nil {
571 return fmt.Errorf("could not update network: %v", err)
572 }
573
574 sendServicePRIVMSG(dc, fmt.Sprintf("updated network %q", network.GetName()))
575 return nil
576}
577
578func handleServiceNetworkDelete(dc *downstreamConn, params []string) error {
579 if len(params) != 1 {
580 return fmt.Errorf("expected exactly one argument")
581 }
582
583 net := dc.user.getNetwork(params[0])
584 if net == nil {
585 return fmt.Errorf("unknown network %q", params[0])
586 }
587
588 if err := dc.user.deleteNetwork(net.ID); err != nil {
589 return err
590 }
591
592 sendServicePRIVMSG(dc, fmt.Sprintf("deleted network %q", net.GetName()))
593 return nil
594}
595
596func handleServiceNetworkQuote(dc *downstreamConn, params []string) error {
597 if len(params) != 2 {
598 return fmt.Errorf("expected exactly two arguments")
599 }
600
601 net := dc.user.getNetwork(params[0])
602 if net == nil {
603 return fmt.Errorf("unknown network %q", params[0])
604 }
605
606 uc := net.conn
607 if uc == nil {
608 return fmt.Errorf("network %q is not currently connected", params[0])
609 }
610
611 m, err := irc.ParseMessage(params[1])
612 if err != nil {
613 return fmt.Errorf("failed to parse command %q: %v", params[1], err)
614 }
615 uc.SendMessage(m)
616
617 sendServicePRIVMSG(dc, fmt.Sprintf("sent command to %q", net.GetName()))
618 return nil
619}
620
621func sendCertfpFingerprints(dc *downstreamConn, cert []byte) {
622 sha1Sum := sha1.Sum(cert)
623 sendServicePRIVMSG(dc, "SHA-1 fingerprint: "+hex.EncodeToString(sha1Sum[:]))
624 sha256Sum := sha256.Sum256(cert)
625 sendServicePRIVMSG(dc, "SHA-256 fingerprint: "+hex.EncodeToString(sha256Sum[:]))
626 sha512Sum := sha512.Sum512(cert)
627 sendServicePRIVMSG(dc, "SHA-512 fingerprint: "+hex.EncodeToString(sha512Sum[:]))
628}
629
630func handleServiceCertFPGenerate(dc *downstreamConn, params []string) error {
631 fs := newFlagSet()
632 keyType := fs.String("key-type", "rsa", "key type to generate (rsa, ecdsa, ed25519)")
633 bits := fs.Int("bits", 3072, "size of key to generate, meaningful only for RSA")
634
635 if err := fs.Parse(params); err != nil {
636 return err
637 }
638
639 if len(fs.Args()) != 1 {
640 return errors.New("exactly one argument is required")
641 }
642
643 net := dc.user.getNetwork(fs.Arg(0))
644 if net == nil {
645 return fmt.Errorf("unknown network %q", fs.Arg(0))
646 }
647
648 if *bits <= 0 || *bits > maxRSABits {
649 return fmt.Errorf("invalid value for -bits")
650 }
651
652 privKey, cert, err := generateCertFP(*keyType, *bits)
653 if err != nil {
654 return err
655 }
656
657 net.SASL.External.CertBlob = cert
658 net.SASL.External.PrivKeyBlob = privKey
659 net.SASL.Mechanism = "EXTERNAL"
660
661 if err := dc.srv.db.StoreNetwork(context.TODO(), dc.user.ID, &net.Network); err != nil {
662 return err
663 }
664
665 sendServicePRIVMSG(dc, "certificate generated")
666 sendCertfpFingerprints(dc, cert)
667 return nil
668}
669
670func handleServiceCertFPFingerprints(dc *downstreamConn, params []string) error {
671 if len(params) != 1 {
672 return fmt.Errorf("expected exactly one argument")
673 }
674
675 net := dc.user.getNetwork(params[0])
676 if net == nil {
677 return fmt.Errorf("unknown network %q", params[0])
678 }
679
680 if net.SASL.Mechanism != "EXTERNAL" {
681 return fmt.Errorf("CertFP not set up")
682 }
683
684 sendCertfpFingerprints(dc, net.SASL.External.CertBlob)
685 return nil
686}
687
688func handleServiceSASLSetPlain(dc *downstreamConn, params []string) error {
689 if len(params) != 3 {
690 return fmt.Errorf("expected exactly 3 arguments")
691 }
692
693 net := dc.user.getNetwork(params[0])
694 if net == nil {
695 return fmt.Errorf("unknown network %q", params[0])
696 }
697
698 net.SASL.Plain.Username = params[1]
699 net.SASL.Plain.Password = params[2]
700 net.SASL.Mechanism = "PLAIN"
701
702 if err := dc.srv.db.StoreNetwork(context.TODO(), dc.user.ID, &net.Network); err != nil {
703 return err
704 }
705
706 sendServicePRIVMSG(dc, "credentials saved")
707 return nil
708}
709
710func handleServiceSASLReset(dc *downstreamConn, params []string) error {
711 if len(params) != 1 {
712 return fmt.Errorf("expected exactly one argument")
713 }
714
715 net := dc.user.getNetwork(params[0])
716 if net == nil {
717 return fmt.Errorf("unknown network %q", params[0])
718 }
719
720 net.SASL.Plain.Username = ""
721 net.SASL.Plain.Password = ""
722 net.SASL.External.CertBlob = nil
723 net.SASL.External.PrivKeyBlob = nil
724 net.SASL.Mechanism = ""
725
726 if err := dc.srv.db.StoreNetwork(context.TODO(), dc.user.ID, &net.Network); err != nil {
727 return err
728 }
729
730 sendServicePRIVMSG(dc, "credentials reset")
731 return nil
732}
733
734func handleUserCreate(dc *downstreamConn, params []string) error {
735 fs := newFlagSet()
736 username := fs.String("username", "", "")
737 password := fs.String("password", "", "")
738 realname := fs.String("realname", "", "")
739 admin := fs.Bool("admin", false, "")
740
741 if err := fs.Parse(params); err != nil {
742 return err
743 }
744 if *username == "" {
745 return fmt.Errorf("flag -username is required")
746 }
747 if *password == "" {
748 return fmt.Errorf("flag -password is required")
749 }
750
751 hashed, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
752 if err != nil {
753 return fmt.Errorf("failed to hash password: %v", err)
754 }
755
756 user := &User{
757 Username: *username,
758 Password: string(hashed),
759 Realname: *realname,
760 Admin: *admin,
761 }
762 if _, err := dc.srv.createUser(user); err != nil {
763 return fmt.Errorf("could not create user: %v", err)
764 }
765
766 sendServicePRIVMSG(dc, fmt.Sprintf("created user %q", *username))
767 return nil
768}
769
770func popArg(params []string) (string, []string) {
771 if len(params) > 0 && !strings.HasPrefix(params[0], "-") {
772 return params[0], params[1:]
773 }
774 return "", params
775}
776
777func handleUserUpdate(dc *downstreamConn, params []string) error {
778 var password, realname *string
779 var admin *bool
780 fs := newFlagSet()
781 fs.Var(stringPtrFlag{&password}, "password", "")
782 fs.Var(stringPtrFlag{&realname}, "realname", "")
783 fs.Var(boolPtrFlag{&admin}, "admin", "")
784
785 username, params := popArg(params)
786 if err := fs.Parse(params); err != nil {
787 return err
788 }
789 if len(fs.Args()) > 0 {
790 return fmt.Errorf("unexpected argument")
791 }
792
793 var hashed *string
794 if password != nil {
795 hashedBytes, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
796 if err != nil {
797 return fmt.Errorf("failed to hash password: %v", err)
798 }
799 hashedStr := string(hashedBytes)
800 hashed = &hashedStr
801 }
802
803 if username != "" && username != dc.user.Username {
804 if !dc.user.Admin {
805 return fmt.Errorf("you must be an admin to update other users")
806 }
807 if realname != nil {
808 return fmt.Errorf("cannot update -realname of other user")
809 }
810
811 u := dc.srv.getUser(username)
812 if u == nil {
813 return fmt.Errorf("unknown username %q", username)
814 }
815
816 done := make(chan error, 1)
817 u.events <- eventUserUpdate{
818 password: hashed,
819 admin: admin,
820 done: done,
821 }
822 if err := <-done; err != nil {
823 return err
824 }
825
826 sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", username))
827 } else {
828 // copy the user record because we'll mutate it
829 record := dc.user.User
830
831 if hashed != nil {
832 record.Password = *hashed
833 }
834 if realname != nil {
835 record.Realname = *realname
836 }
837 if admin != nil {
838 return fmt.Errorf("cannot update -admin of own user")
839 }
840
841 if err := dc.user.updateUser(&record); err != nil {
842 return err
843 }
844
845 sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", dc.user.Username))
846 }
847
848 return nil
849}
850
851func handleUserDelete(dc *downstreamConn, params []string) error {
852 if len(params) != 1 {
853 return fmt.Errorf("expected exactly one argument")
854 }
855 username := params[0]
856
857 u := dc.srv.getUser(username)
858 if u == nil {
859 return fmt.Errorf("unknown username %q", username)
860 }
861
862 u.stop()
863
864 if err := dc.srv.db.DeleteUser(context.TODO(), u.ID); err != nil {
865 return fmt.Errorf("failed to delete user: %v", err)
866 }
867
868 sendServicePRIVMSG(dc, fmt.Sprintf("deleted user %q", username))
869 return nil
870}
871
872func handleServiceChannelStatus(dc *downstreamConn, params []string) error {
873 var defaultNetworkName string
874 if dc.network != nil {
875 defaultNetworkName = dc.network.GetName()
876 }
877
878 fs := newFlagSet()
879 networkName := fs.String("network", defaultNetworkName, "")
880
881 if err := fs.Parse(params); err != nil {
882 return err
883 }
884
885 n := 0
886
887 sendNetwork := func(net *network) {
888 var channels []*Channel
889 for _, entry := range net.channels.innerMap {
890 channels = append(channels, entry.value.(*Channel))
891 }
892
893 sort.Slice(channels, func(i, j int) bool {
894 return strings.ReplaceAll(channels[i].Name, "#", "") <
895 strings.ReplaceAll(channels[j].Name, "#", "")
896 })
897
898 for _, ch := range channels {
899 var uch *upstreamChannel
900 if net.conn != nil {
901 uch = net.conn.channels.Value(ch.Name)
902 }
903
904 name := ch.Name
905 if *networkName == "" {
906 name += "/" + net.GetName()
907 }
908
909 var status string
910 if uch != nil {
911 status = "joined"
912 } else if net.conn != nil {
913 status = "parted"
914 } else {
915 status = "disconnected"
916 }
917
918 if ch.Detached {
919 status += ", detached"
920 }
921
922 s := fmt.Sprintf("%v [%v]", name, status)
923 sendServicePRIVMSG(dc, s)
924
925 n++
926 }
927 }
928
929 if *networkName == "" {
930 dc.user.forEachNetwork(sendNetwork)
931 } else {
932 net := dc.user.getNetwork(*networkName)
933 if net == nil {
934 return fmt.Errorf("unknown network %q", *networkName)
935 }
936 sendNetwork(net)
937 }
938
939 if n == 0 {
940 sendServicePRIVMSG(dc, "No channel configured.")
941 }
942
943 return nil
944}
945
946type channelFlagSet struct {
947 *flag.FlagSet
948 RelayDetached, ReattachOn, DetachAfter, DetachOn *string
949}
950
951func newChannelFlagSet() *channelFlagSet {
952 fs := &channelFlagSet{FlagSet: newFlagSet()}
953 fs.Var(stringPtrFlag{&fs.RelayDetached}, "relay-detached", "")
954 fs.Var(stringPtrFlag{&fs.ReattachOn}, "reattach-on", "")
955 fs.Var(stringPtrFlag{&fs.DetachAfter}, "detach-after", "")
956 fs.Var(stringPtrFlag{&fs.DetachOn}, "detach-on", "")
957 return fs
958}
959
960func (fs *channelFlagSet) update(channel *Channel) error {
961 if fs.RelayDetached != nil {
962 filter, err := parseFilter(*fs.RelayDetached)
963 if err != nil {
964 return err
965 }
966 channel.RelayDetached = filter
967 }
968 if fs.ReattachOn != nil {
969 filter, err := parseFilter(*fs.ReattachOn)
970 if err != nil {
971 return err
972 }
973 channel.ReattachOn = filter
974 }
975 if fs.DetachAfter != nil {
976 dur, err := time.ParseDuration(*fs.DetachAfter)
977 if err != nil || dur < 0 {
978 return fmt.Errorf("unknown duration for -detach-after %q (duration format: 0, 300s, 22h30m, ...)", *fs.DetachAfter)
979 }
980 channel.DetachAfter = dur
981 }
982 if fs.DetachOn != nil {
983 filter, err := parseFilter(*fs.DetachOn)
984 if err != nil {
985 return err
986 }
987 channel.DetachOn = filter
988 }
989 return nil
990}
991
992func handleServiceChannelUpdate(dc *downstreamConn, params []string) error {
993 if len(params) < 1 {
994 return fmt.Errorf("expected at least one argument")
995 }
996 name := params[0]
997
998 fs := newChannelFlagSet()
999 if err := fs.Parse(params[1:]); err != nil {
1000 return err
1001 }
1002
1003 uc, upstreamName, err := dc.unmarshalEntity(name)
1004 if err != nil {
1005 return fmt.Errorf("unknown channel %q", name)
1006 }
1007
1008 ch := uc.network.channels.Value(upstreamName)
1009 if ch == nil {
1010 return fmt.Errorf("unknown channel %q", name)
1011 }
1012
1013 if err := fs.update(ch); err != nil {
1014 return err
1015 }
1016
1017 uc.updateChannelAutoDetach(upstreamName)
1018
1019 if err := dc.srv.db.StoreChannel(context.TODO(), uc.network.ID, ch); err != nil {
1020 return fmt.Errorf("failed to update channel: %v", err)
1021 }
1022
1023 sendServicePRIVMSG(dc, fmt.Sprintf("updated channel %q", name))
1024 return nil
1025}
1026
1027func handleServiceServerStatus(dc *downstreamConn, params []string) error {
1028 dbStats, err := dc.user.srv.db.Stats(context.TODO())
1029 if err != nil {
1030 return err
1031 }
1032 serverStats := dc.user.srv.Stats()
1033 sendServicePRIVMSG(dc, fmt.Sprintf("%v/%v users, %v downstreams, %v networks, %v channels", serverStats.Users, dbStats.Users, serverStats.Downstreams, dbStats.Networks, dbStats.Channels))
1034 return nil
1035}
1036
1037func handleServiceServerNotice(dc *downstreamConn, params []string) error {
1038 if len(params) != 1 {
1039 return fmt.Errorf("expected exactly one argument")
1040 }
1041 text := params[0]
1042
1043 dc.logger.Printf("broadcasting bouncer-wide NOTICE: %v", text)
1044
1045 broadcastMsg := &irc.Message{
1046 Prefix: servicePrefix,
1047 Command: "NOTICE",
1048 Params: []string{"$" + dc.srv.Hostname, text},
1049 }
1050 dc.srv.forEachUser(func(u *user) {
1051 u.events <- eventBroadcast{broadcastMsg}
1052 })
1053 return nil
1054}
Note: See TracBrowser for help on using the repository browser.