source: code/trunk/service.go@ 611

Last change on this file since 611 was 607, checked in by contact, 4 years ago

Add DB stats

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