source: code/trunk/downstream.go@ 554

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

Make user MODE commands fail in multi-upstream mode

References: https://todo.sr.ht/~emersion/soju/20

File size: 59.4 KB
Line 
1package soju
2
3import (
4 "crypto/tls"
5 "encoding/base64"
6 "fmt"
7 "io"
8 "net"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/emersion/go-sasl"
14 "golang.org/x/crypto/bcrypt"
15 "gopkg.in/irc.v3"
16)
17
18type ircError struct {
19 Message *irc.Message
20}
21
22func (err ircError) Error() string {
23 return err.Message.String()
24}
25
26func newUnknownCommandError(cmd string) ircError {
27 return ircError{&irc.Message{
28 Command: irc.ERR_UNKNOWNCOMMAND,
29 Params: []string{
30 "*",
31 cmd,
32 "Unknown command",
33 },
34 }}
35}
36
37func newNeedMoreParamsError(cmd string) ircError {
38 return ircError{&irc.Message{
39 Command: irc.ERR_NEEDMOREPARAMS,
40 Params: []string{
41 "*",
42 cmd,
43 "Not enough parameters",
44 },
45 }}
46}
47
48func newChatHistoryError(subcommand string, target string) ircError {
49 return ircError{&irc.Message{
50 Command: "FAIL",
51 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, target, "Messages could not be retrieved"},
52 }}
53}
54
55var errAuthFailed = ircError{&irc.Message{
56 Command: irc.ERR_PASSWDMISMATCH,
57 Params: []string{"*", "Invalid username or password"},
58}}
59
60func parseBouncerNetID(subcommand, s string) (int64, error) {
61 id, err := strconv.ParseInt(s, 10, 64)
62 if err != nil {
63 return 0, ircError{&irc.Message{
64 Command: "FAIL",
65 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, s, "Invalid network ID"},
66 }}
67 }
68 return id, nil
69}
70
71func getNetworkAttrs(network *network) irc.Tags {
72 state := "disconnected"
73 if uc := network.conn; uc != nil {
74 state = "connected"
75 }
76
77 attrs := irc.Tags{
78 "name": irc.TagValue(network.GetName()),
79 "state": irc.TagValue(state),
80 "nickname": irc.TagValue(network.Nick),
81 }
82
83 if network.Username != "" {
84 attrs["username"] = irc.TagValue(network.Username)
85 }
86 if network.Realname != "" {
87 attrs["realname"] = irc.TagValue(network.Realname)
88 }
89
90 if u, err := network.URL(); err == nil {
91 hasHostPort := true
92 switch u.Scheme {
93 case "ircs":
94 attrs["tls"] = irc.TagValue("1")
95 case "irc+insecure":
96 attrs["tls"] = irc.TagValue("0")
97 default:
98 hasHostPort = false
99 }
100 if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
101 attrs["host"] = irc.TagValue(host)
102 attrs["port"] = irc.TagValue(port)
103 } else if hasHostPort {
104 attrs["host"] = irc.TagValue(u.Host)
105 }
106 }
107
108 return attrs
109}
110
111// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
112// '*' and '?' break masks
113const illegalNickChars = " :@!*?"
114
115// permanentDownstreamCaps is the list of always-supported downstream
116// capabilities.
117var permanentDownstreamCaps = map[string]string{
118 "batch": "",
119 "cap-notify": "",
120 "echo-message": "",
121 "invite-notify": "",
122 "message-tags": "",
123 "sasl": "PLAIN",
124 "server-time": "",
125 "setname": "",
126
127 "soju.im/bouncer-networks": "",
128 "soju.im/bouncer-networks-notify": "",
129}
130
131// needAllDownstreamCaps is the list of downstream capabilities that
132// require support from all upstreams to be enabled
133var needAllDownstreamCaps = map[string]string{
134 "away-notify": "",
135 "extended-join": "",
136 "multi-prefix": "",
137}
138
139// passthroughIsupport is the set of ISUPPORT tokens that are directly passed
140// through from the upstream server to downstream clients.
141//
142// This is only effective in single-upstream mode.
143var passthroughIsupport = map[string]bool{
144 "AWAYLEN": true,
145 "BOT": true,
146 "CHANLIMIT": true,
147 "CHANMODES": true,
148 "CHANNELLEN": true,
149 "CHANTYPES": true,
150 "EXCEPTS": true,
151 "EXTBAN": true,
152 "HOSTLEN": true,
153 "INVEX": true,
154 "KICKLEN": true,
155 "MAXLIST": true,
156 "MAXTARGETS": true,
157 "MODES": true,
158 "NAMELEN": true,
159 "NETWORK": true,
160 "NICKLEN": true,
161 "PREFIX": true,
162 "SAFELIST": true,
163 "TARGMAX": true,
164 "TOPICLEN": true,
165 "USERLEN": true,
166}
167
168type downstreamConn struct {
169 conn
170
171 id uint64
172
173 registered bool
174 user *user
175 nick string
176 nickCM string
177 rawUsername string
178 networkName string
179 clientName string
180 realname string
181 hostname string
182 password string // empty after authentication
183 network *network // can be nil
184
185 negociatingCaps bool
186 capVersion int
187 supportedCaps map[string]string
188 caps map[string]bool
189
190 lastBatchRef uint64
191
192 saslServer sasl.Server
193}
194
195func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
196 remoteAddr := ic.RemoteAddr().String()
197 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
198 options := connOptions{Logger: logger}
199 dc := &downstreamConn{
200 conn: *newConn(srv, ic, &options),
201 id: id,
202 supportedCaps: make(map[string]string),
203 caps: make(map[string]bool),
204 }
205 dc.hostname = remoteAddr
206 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
207 dc.hostname = host
208 }
209 for k, v := range permanentDownstreamCaps {
210 dc.supportedCaps[k] = v
211 }
212 if srv.LogPath != "" {
213 dc.supportedCaps["draft/chathistory"] = ""
214 }
215 return dc
216}
217
218func (dc *downstreamConn) prefix() *irc.Prefix {
219 return &irc.Prefix{
220 Name: dc.nick,
221 User: dc.user.Username,
222 Host: dc.hostname,
223 }
224}
225
226func (dc *downstreamConn) forEachNetwork(f func(*network)) {
227 if dc.network != nil {
228 f(dc.network)
229 } else if !dc.caps["soju.im/bouncer-networks"] {
230 dc.user.forEachNetwork(f)
231 }
232}
233
234func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
235 if dc.network == nil && dc.caps["soju.im/bouncer-networks"] {
236 return
237 }
238 dc.user.forEachUpstream(func(uc *upstreamConn) {
239 if dc.network != nil && uc.network != dc.network {
240 return
241 }
242 f(uc)
243 })
244}
245
246// upstream returns the upstream connection, if any. If there are zero or if
247// there are multiple upstream connections, it returns nil.
248func (dc *downstreamConn) upstream() *upstreamConn {
249 if dc.network == nil {
250 return nil
251 }
252 return dc.network.conn
253}
254
255func isOurNick(net *network, nick string) bool {
256 // TODO: this doesn't account for nick changes
257 if net.conn != nil {
258 return net.casemap(nick) == net.conn.nickCM
259 }
260 // We're not currently connected to the upstream connection, so we don't
261 // know whether this name is our nickname. Best-effort: use the network's
262 // configured nickname and hope it was the one being used when we were
263 // connected.
264 return net.casemap(nick) == net.casemap(net.Nick)
265}
266
267// marshalEntity converts an upstream entity name (ie. channel or nick) into a
268// downstream entity name.
269//
270// This involves adding a "/<network>" suffix if the entity isn't the current
271// user.
272func (dc *downstreamConn) marshalEntity(net *network, name string) string {
273 if isOurNick(net, name) {
274 return dc.nick
275 }
276 name = partialCasemap(net.casemap, name)
277 if dc.network != nil {
278 if dc.network != net {
279 panic("soju: tried to marshal an entity for another network")
280 }
281 return name
282 }
283 return name + "/" + net.GetName()
284}
285
286func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
287 if isOurNick(net, prefix.Name) {
288 return dc.prefix()
289 }
290 prefix.Name = partialCasemap(net.casemap, prefix.Name)
291 if dc.network != nil {
292 if dc.network != net {
293 panic("soju: tried to marshal a user prefix for another network")
294 }
295 return prefix
296 }
297 return &irc.Prefix{
298 Name: prefix.Name + "/" + net.GetName(),
299 User: prefix.User,
300 Host: prefix.Host,
301 }
302}
303
304// unmarshalEntity converts a downstream entity name (ie. channel or nick) into
305// an upstream entity name.
306//
307// This involves removing the "/<network>" suffix.
308func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
309 if uc := dc.upstream(); uc != nil {
310 return uc, name, nil
311 }
312 if dc.network != nil {
313 return nil, "", ircError{&irc.Message{
314 Command: irc.ERR_NOSUCHCHANNEL,
315 Params: []string{name, "Disconnected from upstream network"},
316 }}
317 }
318
319 var conn *upstreamConn
320 if i := strings.LastIndexByte(name, '/'); i >= 0 {
321 network := name[i+1:]
322 name = name[:i]
323
324 dc.forEachUpstream(func(uc *upstreamConn) {
325 if network != uc.network.GetName() {
326 return
327 }
328 conn = uc
329 })
330 }
331
332 if conn == nil {
333 return nil, "", ircError{&irc.Message{
334 Command: irc.ERR_NOSUCHCHANNEL,
335 Params: []string{name, "Missing network suffix in channel name"},
336 }}
337 }
338 return conn, name, nil
339}
340
341func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
342 if dc.upstream() != nil {
343 return text
344 }
345 // TODO: smarter parsing that ignores URLs
346 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
347}
348
349func (dc *downstreamConn) readMessages(ch chan<- event) error {
350 for {
351 msg, err := dc.ReadMessage()
352 if err == io.EOF {
353 break
354 } else if err != nil {
355 return fmt.Errorf("failed to read IRC command: %v", err)
356 }
357
358 ch <- eventDownstreamMessage{msg, dc}
359 }
360
361 return nil
362}
363
364// SendMessage sends an outgoing message.
365//
366// This can only called from the user goroutine.
367func (dc *downstreamConn) SendMessage(msg *irc.Message) {
368 if !dc.caps["message-tags"] {
369 if msg.Command == "TAGMSG" {
370 return
371 }
372 msg = msg.Copy()
373 for name := range msg.Tags {
374 supported := false
375 switch name {
376 case "time":
377 supported = dc.caps["server-time"]
378 }
379 if !supported {
380 delete(msg.Tags, name)
381 }
382 }
383 }
384 if !dc.caps["batch"] && msg.Tags["batch"] != "" {
385 msg = msg.Copy()
386 delete(msg.Tags, "batch")
387 }
388 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
389 msg.Params = msg.Params[:1]
390 }
391 if msg.Command == "SETNAME" && !dc.caps["setname"] {
392 return
393 }
394
395 dc.conn.SendMessage(msg)
396}
397
398func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef irc.TagValue)) {
399 dc.lastBatchRef++
400 ref := fmt.Sprintf("%v", dc.lastBatchRef)
401
402 if dc.caps["batch"] {
403 dc.SendMessage(&irc.Message{
404 Tags: tags,
405 Prefix: dc.srv.prefix(),
406 Command: "BATCH",
407 Params: append([]string{"+" + ref, typ}, params...),
408 })
409 }
410
411 f(irc.TagValue(ref))
412
413 if dc.caps["batch"] {
414 dc.SendMessage(&irc.Message{
415 Prefix: dc.srv.prefix(),
416 Command: "BATCH",
417 Params: []string{"-" + ref},
418 })
419 }
420}
421
422// sendMessageWithID sends an outgoing message with the specified internal ID.
423func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
424 dc.SendMessage(msg)
425
426 if id == "" || !dc.messageSupportsHistory(msg) {
427 return
428 }
429
430 dc.sendPing(id)
431}
432
433// advanceMessageWithID advances history to the specified message ID without
434// sending a message. This is useful e.g. for self-messages when echo-message
435// isn't enabled.
436func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
437 if id == "" || !dc.messageSupportsHistory(msg) {
438 return
439 }
440
441 dc.sendPing(id)
442}
443
444// ackMsgID acknowledges that a message has been received.
445func (dc *downstreamConn) ackMsgID(id string) {
446 netID, entity, err := parseMsgID(id, nil)
447 if err != nil {
448 dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
449 return
450 }
451
452 network := dc.user.getNetworkByID(netID)
453 if network == nil {
454 return
455 }
456
457 network.delivered.StoreID(entity, dc.clientName, id)
458}
459
460func (dc *downstreamConn) sendPing(msgID string) {
461 token := "soju-msgid-" + msgID
462 dc.SendMessage(&irc.Message{
463 Command: "PING",
464 Params: []string{token},
465 })
466}
467
468func (dc *downstreamConn) handlePong(token string) {
469 if !strings.HasPrefix(token, "soju-msgid-") {
470 dc.logger.Printf("received unrecognized PONG token %q", token)
471 return
472 }
473 msgID := strings.TrimPrefix(token, "soju-msgid-")
474 dc.ackMsgID(msgID)
475}
476
477// marshalMessage re-formats a message coming from an upstream connection so
478// that it's suitable for being sent on this downstream connection. Only
479// messages that may appear in logs are supported, except MODE.
480func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
481 msg = msg.Copy()
482 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
483
484 switch msg.Command {
485 case "PRIVMSG", "NOTICE", "TAGMSG":
486 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
487 case "NICK":
488 // Nick change for another user
489 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
490 case "JOIN", "PART":
491 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
492 case "KICK":
493 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
494 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
495 case "TOPIC":
496 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
497 case "QUIT", "SETNAME":
498 // This space is intentionally left blank
499 default:
500 panic(fmt.Sprintf("unexpected %q message", msg.Command))
501 }
502
503 return msg
504}
505
506func (dc *downstreamConn) handleMessage(msg *irc.Message) error {
507 switch msg.Command {
508 case "QUIT":
509 return dc.Close()
510 default:
511 if dc.registered {
512 return dc.handleMessageRegistered(msg)
513 } else {
514 return dc.handleMessageUnregistered(msg)
515 }
516 }
517}
518
519func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
520 switch msg.Command {
521 case "NICK":
522 var nick string
523 if err := parseMessageParams(msg, &nick); err != nil {
524 return err
525 }
526 if strings.ContainsAny(nick, illegalNickChars) {
527 return ircError{&irc.Message{
528 Command: irc.ERR_ERRONEUSNICKNAME,
529 Params: []string{dc.nick, nick, "contains illegal characters"},
530 }}
531 }
532 nickCM := casemapASCII(nick)
533 if nickCM == serviceNickCM {
534 return ircError{&irc.Message{
535 Command: irc.ERR_NICKNAMEINUSE,
536 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
537 }}
538 }
539 dc.nick = nick
540 dc.nickCM = nickCM
541 case "USER":
542 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
543 return err
544 }
545 case "PASS":
546 if err := parseMessageParams(msg, &dc.password); err != nil {
547 return err
548 }
549 case "CAP":
550 var subCmd string
551 if err := parseMessageParams(msg, &subCmd); err != nil {
552 return err
553 }
554 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
555 return err
556 }
557 case "AUTHENTICATE":
558 if !dc.caps["sasl"] {
559 return ircError{&irc.Message{
560 Command: irc.ERR_SASLFAIL,
561 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
562 }}
563 }
564 if len(msg.Params) == 0 {
565 return ircError{&irc.Message{
566 Command: irc.ERR_SASLFAIL,
567 Params: []string{"*", "Missing AUTHENTICATE argument"},
568 }}
569 }
570 if dc.nick == "" {
571 return ircError{&irc.Message{
572 Command: irc.ERR_SASLFAIL,
573 Params: []string{"*", "Expected NICK command before AUTHENTICATE"},
574 }}
575 }
576
577 var resp []byte
578 if dc.saslServer == nil {
579 mech := strings.ToUpper(msg.Params[0])
580 switch mech {
581 case "PLAIN":
582 dc.saslServer = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
583 return dc.authenticate(username, password)
584 }))
585 default:
586 return ircError{&irc.Message{
587 Command: irc.ERR_SASLFAIL,
588 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
589 }}
590 }
591 } else if msg.Params[0] == "*" {
592 dc.saslServer = nil
593 return ircError{&irc.Message{
594 Command: irc.ERR_SASLABORTED,
595 Params: []string{"*", "SASL authentication aborted"},
596 }}
597 } else if msg.Params[0] == "+" {
598 resp = nil
599 } else {
600 // TODO: multi-line messages
601 var err error
602 resp, err = base64.StdEncoding.DecodeString(msg.Params[0])
603 if err != nil {
604 dc.saslServer = nil
605 return ircError{&irc.Message{
606 Command: irc.ERR_SASLFAIL,
607 Params: []string{"*", "Invalid base64-encoded response"},
608 }}
609 }
610 }
611
612 challenge, done, err := dc.saslServer.Next(resp)
613 if err != nil {
614 dc.saslServer = nil
615 if ircErr, ok := err.(ircError); ok && ircErr.Message.Command == irc.ERR_PASSWDMISMATCH {
616 return ircError{&irc.Message{
617 Command: irc.ERR_SASLFAIL,
618 Params: []string{"*", ircErr.Message.Params[1]},
619 }}
620 }
621 dc.SendMessage(&irc.Message{
622 Prefix: dc.srv.prefix(),
623 Command: irc.ERR_SASLFAIL,
624 Params: []string{"*", "SASL error"},
625 })
626 return fmt.Errorf("SASL authentication failed: %v", err)
627 } else if done {
628 dc.saslServer = nil
629 dc.SendMessage(&irc.Message{
630 Prefix: dc.srv.prefix(),
631 Command: irc.RPL_LOGGEDIN,
632 Params: []string{dc.nick, dc.prefix().String(), dc.user.Username, "You are now logged in"},
633 })
634 dc.SendMessage(&irc.Message{
635 Prefix: dc.srv.prefix(),
636 Command: irc.RPL_SASLSUCCESS,
637 Params: []string{dc.nick, "SASL authentication successful"},
638 })
639 } else {
640 challengeStr := "+"
641 if len(challenge) > 0 {
642 challengeStr = base64.StdEncoding.EncodeToString(challenge)
643 }
644
645 // TODO: multi-line messages
646 dc.SendMessage(&irc.Message{
647 Prefix: dc.srv.prefix(),
648 Command: "AUTHENTICATE",
649 Params: []string{challengeStr},
650 })
651 }
652 case "BOUNCER":
653 var subcommand string
654 if err := parseMessageParams(msg, &subcommand); err != nil {
655 return err
656 }
657
658 switch strings.ToUpper(subcommand) {
659 case "BIND":
660 var idStr string
661 if err := parseMessageParams(msg, nil, &idStr); err != nil {
662 return err
663 }
664
665 if dc.registered {
666 return ircError{&irc.Message{
667 Command: "FAIL",
668 Params: []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind bouncer network after registration"},
669 }}
670 }
671 if dc.user == nil {
672 return ircError{&irc.Message{
673 Command: "FAIL",
674 Params: []string{"BOUNCER", "ACCOUNT_REQUIRED", "BIND", "Authentication needed to bind to bouncer network"},
675 }}
676 }
677
678 id, err := parseBouncerNetID(subcommand, idStr)
679 if err != nil {
680 return err
681 }
682
683 var match *network
684 dc.user.forEachNetwork(func(net *network) {
685 if net.ID == id {
686 match = net
687 }
688 })
689 if match == nil {
690 return ircError{&irc.Message{
691 Command: "FAIL",
692 Params: []string{"BOUNCER", "INVALID_NETID", idStr, "Unknown network ID"},
693 }}
694 }
695
696 dc.networkName = match.GetName()
697 }
698 default:
699 dc.logger.Printf("unhandled message: %v", msg)
700 return newUnknownCommandError(msg.Command)
701 }
702 if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
703 return dc.register()
704 }
705 return nil
706}
707
708func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
709 cmd = strings.ToUpper(cmd)
710
711 replyTo := dc.nick
712 if !dc.registered {
713 replyTo = "*"
714 }
715
716 switch cmd {
717 case "LS":
718 if len(args) > 0 {
719 var err error
720 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
721 return err
722 }
723 }
724 if !dc.registered && dc.capVersion >= 302 {
725 // Let downstream show everything it supports, and trim
726 // down the available capabilities when upstreams are
727 // known.
728 for k, v := range needAllDownstreamCaps {
729 dc.supportedCaps[k] = v
730 }
731 }
732
733 caps := make([]string, 0, len(dc.supportedCaps))
734 for k, v := range dc.supportedCaps {
735 if dc.capVersion >= 302 && v != "" {
736 caps = append(caps, k+"="+v)
737 } else {
738 caps = append(caps, k)
739 }
740 }
741
742 // TODO: multi-line replies
743 dc.SendMessage(&irc.Message{
744 Prefix: dc.srv.prefix(),
745 Command: "CAP",
746 Params: []string{replyTo, "LS", strings.Join(caps, " ")},
747 })
748
749 if dc.capVersion >= 302 {
750 // CAP version 302 implicitly enables cap-notify
751 dc.caps["cap-notify"] = true
752 }
753
754 if !dc.registered {
755 dc.negociatingCaps = true
756 }
757 case "LIST":
758 var caps []string
759 for name, enabled := range dc.caps {
760 if enabled {
761 caps = append(caps, name)
762 }
763 }
764
765 // TODO: multi-line replies
766 dc.SendMessage(&irc.Message{
767 Prefix: dc.srv.prefix(),
768 Command: "CAP",
769 Params: []string{replyTo, "LIST", strings.Join(caps, " ")},
770 })
771 case "REQ":
772 if len(args) == 0 {
773 return ircError{&irc.Message{
774 Command: err_invalidcapcmd,
775 Params: []string{replyTo, cmd, "Missing argument in CAP REQ command"},
776 }}
777 }
778
779 // TODO: atomically ack/nak the whole capability set
780 caps := strings.Fields(args[0])
781 ack := true
782 for _, name := range caps {
783 name = strings.ToLower(name)
784 enable := !strings.HasPrefix(name, "-")
785 if !enable {
786 name = strings.TrimPrefix(name, "-")
787 }
788
789 if enable == dc.caps[name] {
790 continue
791 }
792
793 _, ok := dc.supportedCaps[name]
794 if !ok {
795 ack = false
796 break
797 }
798
799 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
800 // cap-notify cannot be disabled with CAP version 302
801 ack = false
802 break
803 }
804
805 dc.caps[name] = enable
806 }
807
808 reply := "NAK"
809 if ack {
810 reply = "ACK"
811 }
812 dc.SendMessage(&irc.Message{
813 Prefix: dc.srv.prefix(),
814 Command: "CAP",
815 Params: []string{replyTo, reply, args[0]},
816 })
817 case "END":
818 dc.negociatingCaps = false
819 default:
820 return ircError{&irc.Message{
821 Command: err_invalidcapcmd,
822 Params: []string{replyTo, cmd, "Unknown CAP command"},
823 }}
824 }
825 return nil
826}
827
828func (dc *downstreamConn) setSupportedCap(name, value string) {
829 prevValue, hasPrev := dc.supportedCaps[name]
830 changed := !hasPrev || prevValue != value
831 dc.supportedCaps[name] = value
832
833 if !dc.caps["cap-notify"] || !changed {
834 return
835 }
836
837 replyTo := dc.nick
838 if !dc.registered {
839 replyTo = "*"
840 }
841
842 cap := name
843 if value != "" && dc.capVersion >= 302 {
844 cap = name + "=" + value
845 }
846
847 dc.SendMessage(&irc.Message{
848 Prefix: dc.srv.prefix(),
849 Command: "CAP",
850 Params: []string{replyTo, "NEW", cap},
851 })
852}
853
854func (dc *downstreamConn) unsetSupportedCap(name string) {
855 _, hasPrev := dc.supportedCaps[name]
856 delete(dc.supportedCaps, name)
857 delete(dc.caps, name)
858
859 if !dc.caps["cap-notify"] || !hasPrev {
860 return
861 }
862
863 replyTo := dc.nick
864 if !dc.registered {
865 replyTo = "*"
866 }
867
868 dc.SendMessage(&irc.Message{
869 Prefix: dc.srv.prefix(),
870 Command: "CAP",
871 Params: []string{replyTo, "DEL", name},
872 })
873}
874
875func (dc *downstreamConn) updateSupportedCaps() {
876 supportedCaps := make(map[string]bool)
877 for cap := range needAllDownstreamCaps {
878 supportedCaps[cap] = true
879 }
880 dc.forEachUpstream(func(uc *upstreamConn) {
881 for cap, supported := range supportedCaps {
882 supportedCaps[cap] = supported && uc.caps[cap]
883 }
884 })
885
886 for cap, supported := range supportedCaps {
887 if supported {
888 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
889 } else {
890 dc.unsetSupportedCap(cap)
891 }
892 }
893}
894
895func (dc *downstreamConn) updateNick() {
896 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
897 dc.SendMessage(&irc.Message{
898 Prefix: dc.prefix(),
899 Command: "NICK",
900 Params: []string{uc.nick},
901 })
902 dc.nick = uc.nick
903 dc.nickCM = casemapASCII(dc.nick)
904 }
905}
906
907func (dc *downstreamConn) updateRealname() {
908 if uc := dc.upstream(); uc != nil && uc.realname != dc.realname && dc.caps["setname"] {
909 dc.SendMessage(&irc.Message{
910 Prefix: dc.prefix(),
911 Command: "SETNAME",
912 Params: []string{uc.realname},
913 })
914 dc.realname = uc.realname
915 }
916}
917
918func sanityCheckServer(addr string) error {
919 dialer := net.Dialer{Timeout: 30 * time.Second}
920 conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
921 if err != nil {
922 return err
923 }
924 return conn.Close()
925}
926
927func unmarshalUsername(rawUsername string) (username, client, network string) {
928 username = rawUsername
929
930 i := strings.IndexAny(username, "/@")
931 j := strings.LastIndexAny(username, "/@")
932 if i >= 0 {
933 username = rawUsername[:i]
934 }
935 if j >= 0 {
936 if rawUsername[j] == '@' {
937 client = rawUsername[j+1:]
938 } else {
939 network = rawUsername[j+1:]
940 }
941 }
942 if i >= 0 && j >= 0 && i < j {
943 if rawUsername[i] == '@' {
944 client = rawUsername[i+1 : j]
945 } else {
946 network = rawUsername[i+1 : j]
947 }
948 }
949
950 return username, client, network
951}
952
953func (dc *downstreamConn) authenticate(username, password string) error {
954 username, clientName, networkName := unmarshalUsername(username)
955
956 u, err := dc.srv.db.GetUser(username)
957 if err != nil {
958 dc.logger.Printf("failed authentication for %q: user not found: %v", username, err)
959 return errAuthFailed
960 }
961
962 // Password auth disabled
963 if u.Password == "" {
964 return errAuthFailed
965 }
966
967 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
968 if err != nil {
969 dc.logger.Printf("failed authentication for %q: wrong password: %v", username, err)
970 return errAuthFailed
971 }
972
973 dc.user = dc.srv.getUser(username)
974 if dc.user == nil {
975 dc.logger.Printf("failed authentication for %q: user not active", username)
976 return errAuthFailed
977 }
978 dc.clientName = clientName
979 dc.networkName = networkName
980 return nil
981}
982
983func (dc *downstreamConn) register() error {
984 if dc.registered {
985 return fmt.Errorf("tried to register twice")
986 }
987
988 password := dc.password
989 dc.password = ""
990 if dc.user == nil {
991 if err := dc.authenticate(dc.rawUsername, password); err != nil {
992 return err
993 }
994 }
995
996 if dc.clientName == "" && dc.networkName == "" {
997 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
998 }
999
1000 dc.registered = true
1001 dc.logger.Printf("registration complete for user %q", dc.user.Username)
1002 return nil
1003}
1004
1005func (dc *downstreamConn) loadNetwork() error {
1006 if dc.networkName == "" {
1007 return nil
1008 }
1009
1010 network := dc.user.getNetwork(dc.networkName)
1011 if network == nil {
1012 addr := dc.networkName
1013 if !strings.ContainsRune(addr, ':') {
1014 addr = addr + ":6697"
1015 }
1016
1017 dc.logger.Printf("trying to connect to new network %q", addr)
1018 if err := sanityCheckServer(addr); err != nil {
1019 dc.logger.Printf("failed to connect to %q: %v", addr, err)
1020 return ircError{&irc.Message{
1021 Command: irc.ERR_PASSWDMISMATCH,
1022 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
1023 }}
1024 }
1025
1026 // Some clients only allow specifying the nickname (and use the
1027 // nickname as a username too). Strip the network name from the
1028 // nickname when auto-saving networks.
1029 nick, _, _ := unmarshalUsername(dc.nick)
1030
1031 dc.logger.Printf("auto-saving network %q", dc.networkName)
1032 var err error
1033 network, err = dc.user.createNetwork(&Network{
1034 Addr: dc.networkName,
1035 Nick: nick,
1036 Enabled: true,
1037 })
1038 if err != nil {
1039 return err
1040 }
1041 }
1042
1043 dc.network = network
1044 return nil
1045}
1046
1047func (dc *downstreamConn) welcome() error {
1048 if dc.user == nil || !dc.registered {
1049 panic("tried to welcome an unregistered connection")
1050 }
1051
1052 // TODO: doing this might take some time. We should do it in dc.register
1053 // instead, but we'll potentially be adding a new network and this must be
1054 // done in the user goroutine.
1055 if err := dc.loadNetwork(); err != nil {
1056 return err
1057 }
1058
1059 isupport := []string{
1060 fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit),
1061 "CASEMAPPING=ascii",
1062 }
1063
1064 if dc.network != nil {
1065 isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
1066 }
1067
1068 if uc := dc.upstream(); uc != nil {
1069 for k := range passthroughIsupport {
1070 v, ok := uc.isupport[k]
1071 if !ok {
1072 continue
1073 }
1074 if v != nil {
1075 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
1076 } else {
1077 isupport = append(isupport, k)
1078 }
1079 }
1080 }
1081
1082 dc.SendMessage(&irc.Message{
1083 Prefix: dc.srv.prefix(),
1084 Command: irc.RPL_WELCOME,
1085 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
1086 })
1087 dc.SendMessage(&irc.Message{
1088 Prefix: dc.srv.prefix(),
1089 Command: irc.RPL_YOURHOST,
1090 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
1091 })
1092 dc.SendMessage(&irc.Message{
1093 Prefix: dc.srv.prefix(),
1094 Command: irc.RPL_CREATED,
1095 Params: []string{dc.nick, "Who cares when the server was created?"},
1096 })
1097 dc.SendMessage(&irc.Message{
1098 Prefix: dc.srv.prefix(),
1099 Command: irc.RPL_MYINFO,
1100 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
1101 })
1102 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
1103 dc.SendMessage(msg)
1104 }
1105 if uc := dc.upstream(); uc != nil {
1106 dc.SendMessage(&irc.Message{
1107 Prefix: dc.srv.prefix(),
1108 Command: irc.RPL_UMODEIS,
1109 Params: []string{dc.nick, string(uc.modes)},
1110 })
1111 }
1112 dc.SendMessage(&irc.Message{
1113 Prefix: dc.srv.prefix(),
1114 Command: irc.ERR_NOMOTD,
1115 Params: []string{dc.nick, "Use /motd to read the message of the day"},
1116 })
1117
1118 dc.updateNick()
1119 dc.updateRealname()
1120 dc.updateSupportedCaps()
1121
1122 if dc.caps["soju.im/bouncer-networks-notify"] {
1123 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
1124 dc.user.forEachNetwork(func(network *network) {
1125 idStr := fmt.Sprintf("%v", network.ID)
1126 attrs := getNetworkAttrs(network)
1127 dc.SendMessage(&irc.Message{
1128 Tags: irc.Tags{"batch": batchRef},
1129 Prefix: dc.srv.prefix(),
1130 Command: "BOUNCER",
1131 Params: []string{"NETWORK", idStr, attrs.String()},
1132 })
1133 })
1134 })
1135 }
1136
1137 dc.forEachUpstream(func(uc *upstreamConn) {
1138 for _, entry := range uc.channels.innerMap {
1139 ch := entry.value.(*upstreamChannel)
1140 if !ch.complete {
1141 continue
1142 }
1143 record := uc.network.channels.Value(ch.Name)
1144 if record != nil && record.Detached {
1145 continue
1146 }
1147
1148 dc.SendMessage(&irc.Message{
1149 Prefix: dc.prefix(),
1150 Command: "JOIN",
1151 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1152 })
1153
1154 forwardChannel(dc, ch)
1155 }
1156 })
1157
1158 dc.forEachNetwork(func(net *network) {
1159 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1160 return
1161 }
1162
1163 // Only send history if we're the first connected client with that name
1164 // for the network
1165 firstClient := true
1166 dc.user.forEachDownstream(func(c *downstreamConn) {
1167 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1168 firstClient = false
1169 }
1170 })
1171 if firstClient {
1172 net.delivered.ForEachTarget(func(target string) {
1173 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1174 if lastDelivered == "" {
1175 return
1176 }
1177
1178 dc.sendTargetBacklog(net, target, lastDelivered)
1179
1180 // Fast-forward history to last message
1181 targetCM := net.casemap(target)
1182 lastID, err := dc.user.msgStore.LastMsgID(net, targetCM, time.Now())
1183 if err != nil {
1184 dc.logger.Printf("failed to get last message ID: %v", err)
1185 return
1186 }
1187 net.delivered.StoreID(target, dc.clientName, lastID)
1188 })
1189 }
1190 })
1191
1192 return nil
1193}
1194
1195// messageSupportsHistory checks whether the provided message can be sent as
1196// part of an history batch.
1197func (dc *downstreamConn) messageSupportsHistory(msg *irc.Message) bool {
1198 // Don't replay all messages, because that would mess up client
1199 // state. For instance we just sent the list of users, sending
1200 // PART messages for one of these users would be incorrect.
1201 // TODO: add support for draft/event-playback
1202 switch msg.Command {
1203 case "PRIVMSG", "NOTICE":
1204 return true
1205 }
1206 return false
1207}
1208
1209func (dc *downstreamConn) sendTargetBacklog(net *network, target, msgID string) {
1210 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1211 return
1212 }
1213
1214 ch := net.channels.Value(target)
1215
1216 limit := 4000
1217 targetCM := net.casemap(target)
1218 history, err := dc.user.msgStore.LoadLatestID(net, targetCM, msgID, limit)
1219 if err != nil {
1220 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
1221 return
1222 }
1223
1224 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1225 for _, msg := range history {
1226 if !dc.messageSupportsHistory(msg) {
1227 continue
1228 }
1229
1230 if ch != nil && ch.Detached {
1231 if net.detachedMessageNeedsRelay(ch, msg) {
1232 dc.relayDetachedMessage(net, msg)
1233 }
1234 } else {
1235 if dc.caps["batch"] {
1236 msg.Tags["batch"] = irc.TagValue(batchRef)
1237 }
1238 dc.SendMessage(dc.marshalMessage(msg, net))
1239 }
1240 }
1241 })
1242}
1243
1244func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1245 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1246 return
1247 }
1248
1249 sender := msg.Prefix.Name
1250 target, text := msg.Params[0], msg.Params[1]
1251 if net.isHighlight(msg) {
1252 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1253 } else {
1254 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1255 }
1256}
1257
1258func (dc *downstreamConn) runUntilRegistered() error {
1259 for !dc.registered {
1260 msg, err := dc.ReadMessage()
1261 if err != nil {
1262 return fmt.Errorf("failed to read IRC command: %v", err)
1263 }
1264
1265 err = dc.handleMessage(msg)
1266 if ircErr, ok := err.(ircError); ok {
1267 ircErr.Message.Prefix = dc.srv.prefix()
1268 dc.SendMessage(ircErr.Message)
1269 } else if err != nil {
1270 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1271 }
1272 }
1273
1274 return nil
1275}
1276
1277func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
1278 switch msg.Command {
1279 case "CAP":
1280 var subCmd string
1281 if err := parseMessageParams(msg, &subCmd); err != nil {
1282 return err
1283 }
1284 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1285 return err
1286 }
1287 case "PING":
1288 var source, destination string
1289 if err := parseMessageParams(msg, &source); err != nil {
1290 return err
1291 }
1292 if len(msg.Params) > 1 {
1293 destination = msg.Params[1]
1294 }
1295 if destination != "" && destination != dc.srv.Hostname {
1296 return ircError{&irc.Message{
1297 Command: irc.ERR_NOSUCHSERVER,
1298 Params: []string{dc.nick, destination, "No such server"},
1299 }}
1300 }
1301 dc.SendMessage(&irc.Message{
1302 Prefix: dc.srv.prefix(),
1303 Command: "PONG",
1304 Params: []string{dc.srv.Hostname, source},
1305 })
1306 return nil
1307 case "PONG":
1308 if len(msg.Params) == 0 {
1309 return newNeedMoreParamsError(msg.Command)
1310 }
1311 token := msg.Params[len(msg.Params)-1]
1312 dc.handlePong(token)
1313 case "USER":
1314 return ircError{&irc.Message{
1315 Command: irc.ERR_ALREADYREGISTERED,
1316 Params: []string{dc.nick, "You may not reregister"},
1317 }}
1318 case "NICK":
1319 var rawNick string
1320 if err := parseMessageParams(msg, &rawNick); err != nil {
1321 return err
1322 }
1323
1324 nick := rawNick
1325 var upstream *upstreamConn
1326 if dc.upstream() == nil {
1327 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1328 if err == nil { // NICK nick/network: NICK only on a specific upstream
1329 upstream = uc
1330 nick = unmarshaledNick
1331 }
1332 }
1333
1334 if strings.ContainsAny(nick, illegalNickChars) {
1335 return ircError{&irc.Message{
1336 Command: irc.ERR_ERRONEUSNICKNAME,
1337 Params: []string{dc.nick, rawNick, "contains illegal characters"},
1338 }}
1339 }
1340 if casemapASCII(nick) == serviceNickCM {
1341 return ircError{&irc.Message{
1342 Command: irc.ERR_NICKNAMEINUSE,
1343 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1344 }}
1345 }
1346
1347 var err error
1348 dc.forEachNetwork(func(n *network) {
1349 if err != nil || (upstream != nil && upstream.network != n) {
1350 return
1351 }
1352 n.Nick = nick
1353 err = dc.srv.db.StoreNetwork(dc.user.ID, &n.Network)
1354 })
1355 if err != nil {
1356 return err
1357 }
1358
1359 dc.forEachUpstream(func(uc *upstreamConn) {
1360 if upstream != nil && upstream != uc {
1361 return
1362 }
1363 uc.SendMessageLabeled(dc.id, &irc.Message{
1364 Command: "NICK",
1365 Params: []string{nick},
1366 })
1367 })
1368
1369 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
1370 dc.SendMessage(&irc.Message{
1371 Prefix: dc.prefix(),
1372 Command: "NICK",
1373 Params: []string{nick},
1374 })
1375 dc.nick = nick
1376 dc.nickCM = casemapASCII(dc.nick)
1377 }
1378 case "SETNAME":
1379 var realname string
1380 if err := parseMessageParams(msg, &realname); err != nil {
1381 return err
1382 }
1383
1384 var storeErr error
1385 var needUpdate []Network
1386 dc.forEachNetwork(func(n *network) {
1387 // We only need to call updateNetwork for upstreams that don't
1388 // support setname
1389 if uc := n.conn; uc != nil && uc.caps["setname"] {
1390 uc.SendMessageLabeled(dc.id, &irc.Message{
1391 Command: "SETNAME",
1392 Params: []string{realname},
1393 })
1394
1395 n.Realname = realname
1396 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
1397 dc.logger.Printf("failed to store network realname: %v", err)
1398 storeErr = err
1399 }
1400 return
1401 }
1402
1403 record := n.Network // copy network record because we'll mutate it
1404 record.Realname = realname
1405 needUpdate = append(needUpdate, record)
1406 })
1407
1408 // Walk the network list as a second step, because updateNetwork
1409 // mutates the original list
1410 for _, record := range needUpdate {
1411 if _, err := dc.user.updateNetwork(&record); err != nil {
1412 dc.logger.Printf("failed to update network realname: %v", err)
1413 storeErr = err
1414 }
1415 }
1416 if storeErr != nil {
1417 return ircError{&irc.Message{
1418 Command: "FAIL",
1419 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1420 }}
1421 }
1422
1423 if dc.upstream() == nil && dc.caps["setname"] {
1424 dc.SendMessage(&irc.Message{
1425 Prefix: dc.prefix(),
1426 Command: "SETNAME",
1427 Params: []string{realname},
1428 })
1429 }
1430 case "JOIN":
1431 var namesStr string
1432 if err := parseMessageParams(msg, &namesStr); err != nil {
1433 return err
1434 }
1435
1436 var keys []string
1437 if len(msg.Params) > 1 {
1438 keys = strings.Split(msg.Params[1], ",")
1439 }
1440
1441 for i, name := range strings.Split(namesStr, ",") {
1442 uc, upstreamName, err := dc.unmarshalEntity(name)
1443 if err != nil {
1444 return err
1445 }
1446
1447 var key string
1448 if len(keys) > i {
1449 key = keys[i]
1450 }
1451
1452 if !uc.isChannel(upstreamName) {
1453 dc.SendMessage(&irc.Message{
1454 Prefix: dc.srv.prefix(),
1455 Command: irc.ERR_NOSUCHCHANNEL,
1456 Params: []string{name, "Not a channel name"},
1457 })
1458 continue
1459 }
1460
1461 params := []string{upstreamName}
1462 if key != "" {
1463 params = append(params, key)
1464 }
1465 uc.SendMessageLabeled(dc.id, &irc.Message{
1466 Command: "JOIN",
1467 Params: params,
1468 })
1469
1470 ch := uc.network.channels.Value(upstreamName)
1471 if ch != nil {
1472 // Don't clear the channel key if there's one set
1473 // TODO: add a way to unset the channel key
1474 if key != "" {
1475 ch.Key = key
1476 }
1477 uc.network.attach(ch)
1478 } else {
1479 ch = &Channel{
1480 Name: upstreamName,
1481 Key: key,
1482 }
1483 uc.network.channels.SetValue(upstreamName, ch)
1484 }
1485 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1486 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1487 }
1488 }
1489 case "PART":
1490 var namesStr string
1491 if err := parseMessageParams(msg, &namesStr); err != nil {
1492 return err
1493 }
1494
1495 var reason string
1496 if len(msg.Params) > 1 {
1497 reason = msg.Params[1]
1498 }
1499
1500 for _, name := range strings.Split(namesStr, ",") {
1501 uc, upstreamName, err := dc.unmarshalEntity(name)
1502 if err != nil {
1503 return err
1504 }
1505
1506 if strings.EqualFold(reason, "detach") {
1507 ch := uc.network.channels.Value(upstreamName)
1508 if ch != nil {
1509 uc.network.detach(ch)
1510 } else {
1511 ch = &Channel{
1512 Name: name,
1513 Detached: true,
1514 }
1515 uc.network.channels.SetValue(upstreamName, ch)
1516 }
1517 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1518 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1519 }
1520 } else {
1521 params := []string{upstreamName}
1522 if reason != "" {
1523 params = append(params, reason)
1524 }
1525 uc.SendMessageLabeled(dc.id, &irc.Message{
1526 Command: "PART",
1527 Params: params,
1528 })
1529
1530 if err := uc.network.deleteChannel(upstreamName); err != nil {
1531 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1532 }
1533 }
1534 }
1535 case "KICK":
1536 var channelStr, userStr string
1537 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1538 return err
1539 }
1540
1541 channels := strings.Split(channelStr, ",")
1542 users := strings.Split(userStr, ",")
1543
1544 var reason string
1545 if len(msg.Params) > 2 {
1546 reason = msg.Params[2]
1547 }
1548
1549 if len(channels) != 1 && len(channels) != len(users) {
1550 return ircError{&irc.Message{
1551 Command: irc.ERR_BADCHANMASK,
1552 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1553 }}
1554 }
1555
1556 for i, user := range users {
1557 var channel string
1558 if len(channels) == 1 {
1559 channel = channels[0]
1560 } else {
1561 channel = channels[i]
1562 }
1563
1564 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1565 if err != nil {
1566 return err
1567 }
1568
1569 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1570 if err != nil {
1571 return err
1572 }
1573
1574 if ucChannel != ucUser {
1575 return ircError{&irc.Message{
1576 Command: irc.ERR_USERNOTINCHANNEL,
1577 Params: []string{dc.nick, user, channel, "They are on another network"},
1578 }}
1579 }
1580 uc := ucChannel
1581
1582 params := []string{upstreamChannel, upstreamUser}
1583 if reason != "" {
1584 params = append(params, reason)
1585 }
1586 uc.SendMessageLabeled(dc.id, &irc.Message{
1587 Command: "KICK",
1588 Params: params,
1589 })
1590 }
1591 case "MODE":
1592 var name string
1593 if err := parseMessageParams(msg, &name); err != nil {
1594 return err
1595 }
1596
1597 var modeStr string
1598 if len(msg.Params) > 1 {
1599 modeStr = msg.Params[1]
1600 }
1601
1602 if casemapASCII(name) == dc.nickCM {
1603 if modeStr != "" {
1604 if uc := dc.upstream(); uc != nil {
1605 uc.SendMessageLabeled(dc.id, &irc.Message{
1606 Command: "MODE",
1607 Params: []string{uc.nick, modeStr},
1608 })
1609 } else {
1610 dc.SendMessage(&irc.Message{
1611 Prefix: dc.srv.prefix(),
1612 Command: irc.ERR_UMODEUNKNOWNFLAG,
1613 Params: []string{dc.nick, "Cannot change user mode in multi-upstream mode"},
1614 })
1615 }
1616 } else {
1617 var userMode string
1618 if uc := dc.upstream(); uc != nil {
1619 userMode = string(uc.modes)
1620 }
1621
1622 dc.SendMessage(&irc.Message{
1623 Prefix: dc.srv.prefix(),
1624 Command: irc.RPL_UMODEIS,
1625 Params: []string{dc.nick, userMode},
1626 })
1627 }
1628 return nil
1629 }
1630
1631 uc, upstreamName, err := dc.unmarshalEntity(name)
1632 if err != nil {
1633 return err
1634 }
1635
1636 if !uc.isChannel(upstreamName) {
1637 return ircError{&irc.Message{
1638 Command: irc.ERR_USERSDONTMATCH,
1639 Params: []string{dc.nick, "Cannot change mode for other users"},
1640 }}
1641 }
1642
1643 if modeStr != "" {
1644 params := []string{upstreamName, modeStr}
1645 params = append(params, msg.Params[2:]...)
1646 uc.SendMessageLabeled(dc.id, &irc.Message{
1647 Command: "MODE",
1648 Params: params,
1649 })
1650 } else {
1651 ch := uc.channels.Value(upstreamName)
1652 if ch == nil {
1653 return ircError{&irc.Message{
1654 Command: irc.ERR_NOSUCHCHANNEL,
1655 Params: []string{dc.nick, name, "No such channel"},
1656 }}
1657 }
1658
1659 if ch.modes == nil {
1660 // we haven't received the initial RPL_CHANNELMODEIS yet
1661 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1662 return nil
1663 }
1664
1665 modeStr, modeParams := ch.modes.Format()
1666 params := []string{dc.nick, name, modeStr}
1667 params = append(params, modeParams...)
1668
1669 dc.SendMessage(&irc.Message{
1670 Prefix: dc.srv.prefix(),
1671 Command: irc.RPL_CHANNELMODEIS,
1672 Params: params,
1673 })
1674 if ch.creationTime != "" {
1675 dc.SendMessage(&irc.Message{
1676 Prefix: dc.srv.prefix(),
1677 Command: rpl_creationtime,
1678 Params: []string{dc.nick, name, ch.creationTime},
1679 })
1680 }
1681 }
1682 case "TOPIC":
1683 var channel string
1684 if err := parseMessageParams(msg, &channel); err != nil {
1685 return err
1686 }
1687
1688 uc, upstreamName, err := dc.unmarshalEntity(channel)
1689 if err != nil {
1690 return err
1691 }
1692
1693 if len(msg.Params) > 1 { // setting topic
1694 topic := msg.Params[1]
1695 uc.SendMessageLabeled(dc.id, &irc.Message{
1696 Command: "TOPIC",
1697 Params: []string{upstreamName, topic},
1698 })
1699 } else { // getting topic
1700 ch := uc.channels.Value(upstreamName)
1701 if ch == nil {
1702 return ircError{&irc.Message{
1703 Command: irc.ERR_NOSUCHCHANNEL,
1704 Params: []string{dc.nick, upstreamName, "No such channel"},
1705 }}
1706 }
1707 sendTopic(dc, ch)
1708 }
1709 case "LIST":
1710 // TODO: support ELIST when supported by all upstreams
1711
1712 pl := pendingLIST{
1713 downstreamID: dc.id,
1714 pendingCommands: make(map[int64]*irc.Message),
1715 }
1716 var upstream *upstreamConn
1717 var upstreamChannels map[int64][]string
1718 if len(msg.Params) > 0 {
1719 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1720 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1721 upstream = uc
1722 } else {
1723 upstreamChannels = make(map[int64][]string)
1724 channels := strings.Split(msg.Params[0], ",")
1725 for _, channel := range channels {
1726 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1727 if err != nil {
1728 return err
1729 }
1730 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1731 }
1732 }
1733 }
1734
1735 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1736 dc.forEachUpstream(func(uc *upstreamConn) {
1737 if upstream != nil && upstream != uc {
1738 return
1739 }
1740 var params []string
1741 if upstreamChannels != nil {
1742 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1743 params = []string{strings.Join(channels, ",")}
1744 } else {
1745 return
1746 }
1747 }
1748 pl.pendingCommands[uc.network.ID] = &irc.Message{
1749 Command: "LIST",
1750 Params: params,
1751 }
1752 uc.trySendLIST(dc.id)
1753 })
1754 case "NAMES":
1755 if len(msg.Params) == 0 {
1756 dc.SendMessage(&irc.Message{
1757 Prefix: dc.srv.prefix(),
1758 Command: irc.RPL_ENDOFNAMES,
1759 Params: []string{dc.nick, "*", "End of /NAMES list"},
1760 })
1761 return nil
1762 }
1763
1764 channels := strings.Split(msg.Params[0], ",")
1765 for _, channel := range channels {
1766 uc, upstreamName, err := dc.unmarshalEntity(channel)
1767 if err != nil {
1768 return err
1769 }
1770
1771 ch := uc.channels.Value(upstreamName)
1772 if ch != nil {
1773 sendNames(dc, ch)
1774 } else {
1775 // NAMES on a channel we have not joined, ask upstream
1776 uc.SendMessageLabeled(dc.id, &irc.Message{
1777 Command: "NAMES",
1778 Params: []string{upstreamName},
1779 })
1780 }
1781 }
1782 case "WHO":
1783 if len(msg.Params) == 0 {
1784 // TODO: support WHO without parameters
1785 dc.SendMessage(&irc.Message{
1786 Prefix: dc.srv.prefix(),
1787 Command: irc.RPL_ENDOFWHO,
1788 Params: []string{dc.nick, "*", "End of /WHO list"},
1789 })
1790 return nil
1791 }
1792
1793 // TODO: support WHO masks
1794 entity := msg.Params[0]
1795 entityCM := casemapASCII(entity)
1796
1797 if dc.network == nil && entityCM == dc.nickCM {
1798 // TODO: support AWAY (H/G) in self WHO reply
1799 dc.SendMessage(&irc.Message{
1800 Prefix: dc.srv.prefix(),
1801 Command: irc.RPL_WHOREPLY,
1802 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1803 })
1804 dc.SendMessage(&irc.Message{
1805 Prefix: dc.srv.prefix(),
1806 Command: irc.RPL_ENDOFWHO,
1807 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1808 })
1809 return nil
1810 }
1811 if entityCM == serviceNickCM {
1812 dc.SendMessage(&irc.Message{
1813 Prefix: dc.srv.prefix(),
1814 Command: irc.RPL_WHOREPLY,
1815 Params: []string{serviceNick, "*", servicePrefix.User, servicePrefix.Host, dc.srv.Hostname, serviceNick, "H", "0 " + serviceRealname},
1816 })
1817 dc.SendMessage(&irc.Message{
1818 Prefix: dc.srv.prefix(),
1819 Command: irc.RPL_ENDOFWHO,
1820 Params: []string{dc.nick, serviceNick, "End of /WHO list"},
1821 })
1822 return nil
1823 }
1824
1825 uc, upstreamName, err := dc.unmarshalEntity(entity)
1826 if err != nil {
1827 return err
1828 }
1829
1830 var params []string
1831 if len(msg.Params) == 2 {
1832 params = []string{upstreamName, msg.Params[1]}
1833 } else {
1834 params = []string{upstreamName}
1835 }
1836
1837 uc.SendMessageLabeled(dc.id, &irc.Message{
1838 Command: "WHO",
1839 Params: params,
1840 })
1841 case "WHOIS":
1842 if len(msg.Params) == 0 {
1843 return ircError{&irc.Message{
1844 Command: irc.ERR_NONICKNAMEGIVEN,
1845 Params: []string{dc.nick, "No nickname given"},
1846 }}
1847 }
1848
1849 var target, mask string
1850 if len(msg.Params) == 1 {
1851 target = ""
1852 mask = msg.Params[0]
1853 } else {
1854 target = msg.Params[0]
1855 mask = msg.Params[1]
1856 }
1857 // TODO: support multiple WHOIS users
1858 if i := strings.IndexByte(mask, ','); i >= 0 {
1859 mask = mask[:i]
1860 }
1861
1862 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
1863 dc.SendMessage(&irc.Message{
1864 Prefix: dc.srv.prefix(),
1865 Command: irc.RPL_WHOISUSER,
1866 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1867 })
1868 dc.SendMessage(&irc.Message{
1869 Prefix: dc.srv.prefix(),
1870 Command: irc.RPL_WHOISSERVER,
1871 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1872 })
1873 dc.SendMessage(&irc.Message{
1874 Prefix: dc.srv.prefix(),
1875 Command: irc.RPL_ENDOFWHOIS,
1876 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1877 })
1878 return nil
1879 }
1880
1881 // TODO: support WHOIS masks
1882 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1883 if err != nil {
1884 return err
1885 }
1886
1887 var params []string
1888 if target != "" {
1889 if target == mask { // WHOIS nick nick
1890 params = []string{upstreamNick, upstreamNick}
1891 } else {
1892 params = []string{target, upstreamNick}
1893 }
1894 } else {
1895 params = []string{upstreamNick}
1896 }
1897
1898 uc.SendMessageLabeled(dc.id, &irc.Message{
1899 Command: "WHOIS",
1900 Params: params,
1901 })
1902 case "PRIVMSG":
1903 var targetsStr, text string
1904 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1905 return err
1906 }
1907 tags := copyClientTags(msg.Tags)
1908
1909 for _, name := range strings.Split(targetsStr, ",") {
1910 if dc.network == nil && casemapASCII(name) == dc.nickCM {
1911 dc.SendMessage(msg)
1912 continue
1913 }
1914
1915 if casemapASCII(name) == serviceNickCM {
1916 if dc.caps["echo-message"] {
1917 echoTags := tags.Copy()
1918 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1919 dc.SendMessage(&irc.Message{
1920 Tags: echoTags,
1921 Prefix: dc.prefix(),
1922 Command: "PRIVMSG",
1923 Params: []string{name, text},
1924 })
1925 }
1926 handleServicePRIVMSG(dc, text)
1927 continue
1928 }
1929
1930 uc, upstreamName, err := dc.unmarshalEntity(name)
1931 if err != nil {
1932 return err
1933 }
1934
1935 if uc.network.casemap(upstreamName) == "nickserv" {
1936 dc.handleNickServPRIVMSG(uc, text)
1937 }
1938
1939 unmarshaledText := text
1940 if uc.isChannel(upstreamName) {
1941 unmarshaledText = dc.unmarshalText(uc, text)
1942 }
1943 uc.SendMessageLabeled(dc.id, &irc.Message{
1944 Tags: tags,
1945 Command: "PRIVMSG",
1946 Params: []string{upstreamName, unmarshaledText},
1947 })
1948
1949 echoTags := tags.Copy()
1950 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1951 echoMsg := &irc.Message{
1952 Tags: echoTags,
1953 Prefix: &irc.Prefix{
1954 Name: uc.nick,
1955 User: uc.username,
1956 },
1957 Command: "PRIVMSG",
1958 Params: []string{upstreamName, text},
1959 }
1960 uc.produce(upstreamName, echoMsg, dc)
1961
1962 uc.updateChannelAutoDetach(upstreamName)
1963 }
1964 case "NOTICE":
1965 var targetsStr, text string
1966 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1967 return err
1968 }
1969 tags := copyClientTags(msg.Tags)
1970
1971 for _, name := range strings.Split(targetsStr, ",") {
1972 uc, upstreamName, err := dc.unmarshalEntity(name)
1973 if err != nil {
1974 return err
1975 }
1976
1977 unmarshaledText := text
1978 if uc.isChannel(upstreamName) {
1979 unmarshaledText = dc.unmarshalText(uc, text)
1980 }
1981 uc.SendMessageLabeled(dc.id, &irc.Message{
1982 Tags: tags,
1983 Command: "NOTICE",
1984 Params: []string{upstreamName, unmarshaledText},
1985 })
1986
1987 uc.updateChannelAutoDetach(upstreamName)
1988 }
1989 case "TAGMSG":
1990 var targetsStr string
1991 if err := parseMessageParams(msg, &targetsStr); err != nil {
1992 return err
1993 }
1994 tags := copyClientTags(msg.Tags)
1995
1996 for _, name := range strings.Split(targetsStr, ",") {
1997 uc, upstreamName, err := dc.unmarshalEntity(name)
1998 if err != nil {
1999 return err
2000 }
2001 if _, ok := uc.caps["message-tags"]; !ok {
2002 continue
2003 }
2004
2005 uc.SendMessageLabeled(dc.id, &irc.Message{
2006 Tags: tags,
2007 Command: "TAGMSG",
2008 Params: []string{upstreamName},
2009 })
2010
2011 uc.updateChannelAutoDetach(upstreamName)
2012 }
2013 case "INVITE":
2014 var user, channel string
2015 if err := parseMessageParams(msg, &user, &channel); err != nil {
2016 return err
2017 }
2018
2019 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2020 if err != nil {
2021 return err
2022 }
2023
2024 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2025 if err != nil {
2026 return err
2027 }
2028
2029 if ucChannel != ucUser {
2030 return ircError{&irc.Message{
2031 Command: irc.ERR_USERNOTINCHANNEL,
2032 Params: []string{dc.nick, user, channel, "They are on another network"},
2033 }}
2034 }
2035 uc := ucChannel
2036
2037 uc.SendMessageLabeled(dc.id, &irc.Message{
2038 Command: "INVITE",
2039 Params: []string{upstreamUser, upstreamChannel},
2040 })
2041 case "CHATHISTORY":
2042 var subcommand string
2043 if err := parseMessageParams(msg, &subcommand); err != nil {
2044 return err
2045 }
2046 var target, limitStr string
2047 var boundsStr [2]string
2048 switch subcommand {
2049 case "AFTER", "BEFORE":
2050 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2051 return err
2052 }
2053 case "BETWEEN":
2054 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2055 return err
2056 }
2057 case "TARGETS":
2058 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2059 return err
2060 }
2061 default:
2062 // TODO: support LATEST, AROUND
2063 return ircError{&irc.Message{
2064 Command: "FAIL",
2065 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
2066 }}
2067 }
2068
2069 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2070 if !ok {
2071 return ircError{&irc.Message{
2072 Command: irc.ERR_UNKNOWNCOMMAND,
2073 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
2074 }}
2075 }
2076
2077 uc, entity, err := dc.unmarshalEntity(target)
2078 if err != nil {
2079 return err
2080 }
2081 entity = uc.network.casemap(entity)
2082
2083 // TODO: support msgid criteria
2084 var bounds [2]time.Time
2085 bounds[0] = parseChatHistoryBound(boundsStr[0])
2086 if bounds[0].IsZero() {
2087 return ircError{&irc.Message{
2088 Command: "FAIL",
2089 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
2090 }}
2091 }
2092
2093 if boundsStr[1] != "" {
2094 bounds[1] = parseChatHistoryBound(boundsStr[1])
2095 if bounds[1].IsZero() {
2096 return ircError{&irc.Message{
2097 Command: "FAIL",
2098 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2099 }}
2100 }
2101 }
2102
2103 limit, err := strconv.Atoi(limitStr)
2104 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
2105 return ircError{&irc.Message{
2106 Command: "FAIL",
2107 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
2108 }}
2109 }
2110
2111 var history []*irc.Message
2112 switch subcommand {
2113 case "BEFORE":
2114 history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], time.Time{}, limit)
2115 case "AFTER":
2116 history, err = store.LoadAfterTime(uc.network, entity, bounds[0], time.Now(), limit)
2117 case "BETWEEN":
2118 if bounds[0].Before(bounds[1]) {
2119 history, err = store.LoadAfterTime(uc.network, entity, bounds[0], bounds[1], limit)
2120 } else {
2121 history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], bounds[1], limit)
2122 }
2123 case "TARGETS":
2124 // TODO: support TARGETS in multi-upstream mode
2125 targets, err := store.ListTargets(uc.network, bounds[0], bounds[1], limit)
2126 if err != nil {
2127 dc.logger.Printf("failed fetching targets for chathistory: %v", target, err)
2128 return ircError{&irc.Message{
2129 Command: "FAIL",
2130 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2131 }}
2132 }
2133
2134 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2135 for _, target := range targets {
2136 if ch := uc.network.channels.Value(target.Name); ch != nil && ch.Detached {
2137 continue
2138 }
2139
2140 dc.SendMessage(&irc.Message{
2141 Tags: irc.Tags{"batch": batchRef},
2142 Prefix: dc.srv.prefix(),
2143 Command: "CHATHISTORY",
2144 Params: []string{"TARGETS", target.Name, target.LatestMessage.UTC().Format(serverTimeLayout)},
2145 })
2146 }
2147 })
2148
2149 return nil
2150 }
2151 if err != nil {
2152 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
2153 return newChatHistoryError(subcommand, target)
2154 }
2155
2156 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2157 for _, msg := range history {
2158 msg.Tags["batch"] = batchRef
2159 dc.SendMessage(dc.marshalMessage(msg, uc.network))
2160 }
2161 })
2162 case "BOUNCER":
2163 var subcommand string
2164 if err := parseMessageParams(msg, &subcommand); err != nil {
2165 return err
2166 }
2167
2168 switch strings.ToUpper(subcommand) {
2169 case "LISTNETWORKS":
2170 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
2171 dc.user.forEachNetwork(func(network *network) {
2172 idStr := fmt.Sprintf("%v", network.ID)
2173 attrs := getNetworkAttrs(network)
2174 dc.SendMessage(&irc.Message{
2175 Tags: irc.Tags{"batch": batchRef},
2176 Prefix: dc.srv.prefix(),
2177 Command: "BOUNCER",
2178 Params: []string{"NETWORK", idStr, attrs.String()},
2179 })
2180 })
2181 })
2182 case "ADDNETWORK":
2183 var attrsStr string
2184 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2185 return err
2186 }
2187 attrs := irc.ParseTags(attrsStr)
2188
2189 host, ok := attrs.GetTag("host")
2190 if !ok {
2191 return ircError{&irc.Message{
2192 Command: "FAIL",
2193 Params: []string{"BOUNCER", "NEED_ATTRIBUTE", subcommand, "host", "Missing required host attribute"},
2194 }}
2195 }
2196
2197 addr := host
2198 if port, ok := attrs.GetTag("port"); ok {
2199 addr += ":" + port
2200 }
2201
2202 if tlsStr, ok := attrs.GetTag("tls"); ok && tlsStr == "0" {
2203 addr = "irc+insecure://" + tlsStr
2204 }
2205
2206 nick, ok := attrs.GetTag("nickname")
2207 if !ok {
2208 nick = dc.nick
2209 }
2210
2211 username, _ := attrs.GetTag("username")
2212 realname, _ := attrs.GetTag("realname")
2213 pass, _ := attrs.GetTag("pass")
2214
2215 // TODO: reject unknown attributes
2216
2217 record := &Network{
2218 Addr: addr,
2219 Nick: nick,
2220 Username: username,
2221 Realname: realname,
2222 Pass: pass,
2223 Enabled: true,
2224 }
2225 network, err := dc.user.createNetwork(record)
2226 if err != nil {
2227 return ircError{&irc.Message{
2228 Command: "FAIL",
2229 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2230 }}
2231 }
2232
2233 dc.SendMessage(&irc.Message{
2234 Prefix: dc.srv.prefix(),
2235 Command: "BOUNCER",
2236 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2237 })
2238 case "CHANGENETWORK":
2239 var idStr, attrsStr string
2240 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2241 return err
2242 }
2243 id, err := parseBouncerNetID(subcommand, idStr)
2244 if err != nil {
2245 return err
2246 }
2247 attrs := irc.ParseTags(attrsStr)
2248
2249 net := dc.user.getNetworkByID(id)
2250 if net == nil {
2251 return ircError{&irc.Message{
2252 Command: "FAIL",
2253 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2254 }}
2255 }
2256
2257 record := net.Network // copy network record because we'll mutate it
2258 for k, v := range attrs {
2259 s := string(v)
2260 switch k {
2261 // TODO: host, port, tls
2262 case "nickname":
2263 record.Nick = s
2264 case "username":
2265 record.Username = s
2266 case "realname":
2267 record.Realname = s
2268 case "pass":
2269 record.Pass = s
2270 default:
2271 return ircError{&irc.Message{
2272 Command: "FAIL",
2273 Params: []string{"BOUNCER", "UNKNOWN_ATTRIBUTE", subcommand, k, "Unknown attribute"},
2274 }}
2275 }
2276 }
2277
2278 _, err = dc.user.updateNetwork(&record)
2279 if err != nil {
2280 return ircError{&irc.Message{
2281 Command: "FAIL",
2282 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2283 }}
2284 }
2285
2286 dc.SendMessage(&irc.Message{
2287 Prefix: dc.srv.prefix(),
2288 Command: "BOUNCER",
2289 Params: []string{"CHANGENETWORK", idStr},
2290 })
2291 case "DELNETWORK":
2292 var idStr string
2293 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2294 return err
2295 }
2296 id, err := parseBouncerNetID(subcommand, idStr)
2297 if err != nil {
2298 return err
2299 }
2300
2301 net := dc.user.getNetworkByID(id)
2302 if net == nil {
2303 return ircError{&irc.Message{
2304 Command: "FAIL",
2305 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2306 }}
2307 }
2308
2309 if err := dc.user.deleteNetwork(net.ID); err != nil {
2310 return err
2311 }
2312
2313 dc.SendMessage(&irc.Message{
2314 Prefix: dc.srv.prefix(),
2315 Command: "BOUNCER",
2316 Params: []string{"DELNETWORK", idStr},
2317 })
2318 default:
2319 return ircError{&irc.Message{
2320 Command: "FAIL",
2321 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2322 }}
2323 }
2324 default:
2325 dc.logger.Printf("unhandled message: %v", msg)
2326
2327 // Only forward unknown commands in single-upstream mode
2328 uc := dc.upstream()
2329 if uc == nil {
2330 return newUnknownCommandError(msg.Command)
2331 }
2332
2333 uc.SendMessageLabeled(dc.id, msg)
2334 }
2335 return nil
2336}
2337
2338func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
2339 username, password, ok := parseNickServCredentials(text, uc.nick)
2340 if !ok {
2341 return
2342 }
2343
2344 // User may have e.g. EXTERNAL mechanism configured. We do not want to
2345 // automatically erase the key pair or any other credentials.
2346 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" {
2347 return
2348 }
2349
2350 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
2351 n := uc.network
2352 n.SASL.Mechanism = "PLAIN"
2353 n.SASL.Plain.Username = username
2354 n.SASL.Plain.Password = password
2355 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
2356 dc.logger.Printf("failed to save NickServ credentials: %v", err)
2357 }
2358}
2359
2360func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
2361 fields := strings.Fields(text)
2362 if len(fields) < 2 {
2363 return "", "", false
2364 }
2365 cmd := strings.ToUpper(fields[0])
2366 params := fields[1:]
2367 switch cmd {
2368 case "REGISTER":
2369 username = nick
2370 password = params[0]
2371 case "IDENTIFY":
2372 if len(params) == 1 {
2373 username = nick
2374 password = params[0]
2375 } else {
2376 username = params[0]
2377 password = params[1]
2378 }
2379 case "SET":
2380 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
2381 username = nick
2382 password = params[1]
2383 }
2384 default:
2385 return "", "", false
2386 }
2387 return username, password, true
2388}
Note: See TracBrowser for help on using the repository browser.