source: code/trunk/service.go@ 550

Last change on this file since 550 was 546, checked in by contact, 4 years ago

Send placeholder when no network/channel is returned by BouncerServ

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