source: code/trunk/service.go@ 591

Last change on this file since 591 was 577, checked in by delthas, 4 years ago

service: Introduce network quote

This command enables sending a raw line to a specific network.

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