source: code/trunk/downstream.go@ 552

Last change on this file since 552 was 552, checked in by greg, 4 years ago

Forward MOTD messages downstream

The first MOTD upon connection is ignored, but subsequent MOTD messages
(requested by the "MOTD" message from the client, typically using a
/motd command) are forwarded.

File size: 59.0 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 dc.SendMessage(&irc.Message{
1106 Prefix: dc.srv.prefix(),
1107 Command: irc.ERR_NOMOTD,
1108 Params: []string{dc.nick, "Use /motd to read the message of the day"},
1109 })
1110
1111 dc.updateNick()
1112 dc.updateRealname()
1113 dc.updateSupportedCaps()
1114
1115 if dc.caps["soju.im/bouncer-networks-notify"] {
1116 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
1117 dc.user.forEachNetwork(func(network *network) {
1118 idStr := fmt.Sprintf("%v", network.ID)
1119 attrs := getNetworkAttrs(network)
1120 dc.SendMessage(&irc.Message{
1121 Tags: irc.Tags{"batch": batchRef},
1122 Prefix: dc.srv.prefix(),
1123 Command: "BOUNCER",
1124 Params: []string{"NETWORK", idStr, attrs.String()},
1125 })
1126 })
1127 })
1128 }
1129
1130 dc.forEachUpstream(func(uc *upstreamConn) {
1131 for _, entry := range uc.channels.innerMap {
1132 ch := entry.value.(*upstreamChannel)
1133 if !ch.complete {
1134 continue
1135 }
1136 record := uc.network.channels.Value(ch.Name)
1137 if record != nil && record.Detached {
1138 continue
1139 }
1140
1141 dc.SendMessage(&irc.Message{
1142 Prefix: dc.prefix(),
1143 Command: "JOIN",
1144 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1145 })
1146
1147 forwardChannel(dc, ch)
1148 }
1149 })
1150
1151 dc.forEachNetwork(func(net *network) {
1152 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1153 return
1154 }
1155
1156 // Only send history if we're the first connected client with that name
1157 // for the network
1158 firstClient := true
1159 dc.user.forEachDownstream(func(c *downstreamConn) {
1160 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1161 firstClient = false
1162 }
1163 })
1164 if firstClient {
1165 net.delivered.ForEachTarget(func(target string) {
1166 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1167 if lastDelivered == "" {
1168 return
1169 }
1170
1171 dc.sendTargetBacklog(net, target, lastDelivered)
1172
1173 // Fast-forward history to last message
1174 targetCM := net.casemap(target)
1175 lastID, err := dc.user.msgStore.LastMsgID(net, targetCM, time.Now())
1176 if err != nil {
1177 dc.logger.Printf("failed to get last message ID: %v", err)
1178 return
1179 }
1180 net.delivered.StoreID(target, dc.clientName, lastID)
1181 })
1182 }
1183 })
1184
1185 return nil
1186}
1187
1188// messageSupportsHistory checks whether the provided message can be sent as
1189// part of an history batch.
1190func (dc *downstreamConn) messageSupportsHistory(msg *irc.Message) bool {
1191 // Don't replay all messages, because that would mess up client
1192 // state. For instance we just sent the list of users, sending
1193 // PART messages for one of these users would be incorrect.
1194 // TODO: add support for draft/event-playback
1195 switch msg.Command {
1196 case "PRIVMSG", "NOTICE":
1197 return true
1198 }
1199 return false
1200}
1201
1202func (dc *downstreamConn) sendTargetBacklog(net *network, target, msgID string) {
1203 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1204 return
1205 }
1206
1207 ch := net.channels.Value(target)
1208
1209 limit := 4000
1210 targetCM := net.casemap(target)
1211 history, err := dc.user.msgStore.LoadLatestID(net, targetCM, msgID, limit)
1212 if err != nil {
1213 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
1214 return
1215 }
1216
1217 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1218 for _, msg := range history {
1219 if !dc.messageSupportsHistory(msg) {
1220 continue
1221 }
1222
1223 if ch != nil && ch.Detached {
1224 if net.detachedMessageNeedsRelay(ch, msg) {
1225 dc.relayDetachedMessage(net, msg)
1226 }
1227 } else {
1228 if dc.caps["batch"] {
1229 msg.Tags["batch"] = irc.TagValue(batchRef)
1230 }
1231 dc.SendMessage(dc.marshalMessage(msg, net))
1232 }
1233 }
1234 })
1235}
1236
1237func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1238 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1239 return
1240 }
1241
1242 sender := msg.Prefix.Name
1243 target, text := msg.Params[0], msg.Params[1]
1244 if net.isHighlight(msg) {
1245 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1246 } else {
1247 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1248 }
1249}
1250
1251func (dc *downstreamConn) runUntilRegistered() error {
1252 for !dc.registered {
1253 msg, err := dc.ReadMessage()
1254 if err != nil {
1255 return fmt.Errorf("failed to read IRC command: %v", err)
1256 }
1257
1258 err = dc.handleMessage(msg)
1259 if ircErr, ok := err.(ircError); ok {
1260 ircErr.Message.Prefix = dc.srv.prefix()
1261 dc.SendMessage(ircErr.Message)
1262 } else if err != nil {
1263 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1264 }
1265 }
1266
1267 return nil
1268}
1269
1270func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
1271 switch msg.Command {
1272 case "CAP":
1273 var subCmd string
1274 if err := parseMessageParams(msg, &subCmd); err != nil {
1275 return err
1276 }
1277 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1278 return err
1279 }
1280 case "PING":
1281 var source, destination string
1282 if err := parseMessageParams(msg, &source); err != nil {
1283 return err
1284 }
1285 if len(msg.Params) > 1 {
1286 destination = msg.Params[1]
1287 }
1288 if destination != "" && destination != dc.srv.Hostname {
1289 return ircError{&irc.Message{
1290 Command: irc.ERR_NOSUCHSERVER,
1291 Params: []string{dc.nick, destination, "No such server"},
1292 }}
1293 }
1294 dc.SendMessage(&irc.Message{
1295 Prefix: dc.srv.prefix(),
1296 Command: "PONG",
1297 Params: []string{dc.srv.Hostname, source},
1298 })
1299 return nil
1300 case "PONG":
1301 if len(msg.Params) == 0 {
1302 return newNeedMoreParamsError(msg.Command)
1303 }
1304 token := msg.Params[len(msg.Params)-1]
1305 dc.handlePong(token)
1306 case "USER":
1307 return ircError{&irc.Message{
1308 Command: irc.ERR_ALREADYREGISTERED,
1309 Params: []string{dc.nick, "You may not reregister"},
1310 }}
1311 case "NICK":
1312 var rawNick string
1313 if err := parseMessageParams(msg, &rawNick); err != nil {
1314 return err
1315 }
1316
1317 nick := rawNick
1318 var upstream *upstreamConn
1319 if dc.upstream() == nil {
1320 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1321 if err == nil { // NICK nick/network: NICK only on a specific upstream
1322 upstream = uc
1323 nick = unmarshaledNick
1324 }
1325 }
1326
1327 if strings.ContainsAny(nick, illegalNickChars) {
1328 return ircError{&irc.Message{
1329 Command: irc.ERR_ERRONEUSNICKNAME,
1330 Params: []string{dc.nick, rawNick, "contains illegal characters"},
1331 }}
1332 }
1333 if casemapASCII(nick) == serviceNickCM {
1334 return ircError{&irc.Message{
1335 Command: irc.ERR_NICKNAMEINUSE,
1336 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1337 }}
1338 }
1339
1340 var err error
1341 dc.forEachNetwork(func(n *network) {
1342 if err != nil || (upstream != nil && upstream.network != n) {
1343 return
1344 }
1345 n.Nick = nick
1346 err = dc.srv.db.StoreNetwork(dc.user.ID, &n.Network)
1347 })
1348 if err != nil {
1349 return err
1350 }
1351
1352 dc.forEachUpstream(func(uc *upstreamConn) {
1353 if upstream != nil && upstream != uc {
1354 return
1355 }
1356 uc.SendMessageLabeled(dc.id, &irc.Message{
1357 Command: "NICK",
1358 Params: []string{nick},
1359 })
1360 })
1361
1362 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
1363 dc.SendMessage(&irc.Message{
1364 Prefix: dc.prefix(),
1365 Command: "NICK",
1366 Params: []string{nick},
1367 })
1368 dc.nick = nick
1369 dc.nickCM = casemapASCII(dc.nick)
1370 }
1371 case "SETNAME":
1372 var realname string
1373 if err := parseMessageParams(msg, &realname); err != nil {
1374 return err
1375 }
1376
1377 var storeErr error
1378 var needUpdate []Network
1379 dc.forEachNetwork(func(n *network) {
1380 // We only need to call updateNetwork for upstreams that don't
1381 // support setname
1382 if uc := n.conn; uc != nil && uc.caps["setname"] {
1383 uc.SendMessageLabeled(dc.id, &irc.Message{
1384 Command: "SETNAME",
1385 Params: []string{realname},
1386 })
1387
1388 n.Realname = realname
1389 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
1390 dc.logger.Printf("failed to store network realname: %v", err)
1391 storeErr = err
1392 }
1393 return
1394 }
1395
1396 record := n.Network // copy network record because we'll mutate it
1397 record.Realname = realname
1398 needUpdate = append(needUpdate, record)
1399 })
1400
1401 // Walk the network list as a second step, because updateNetwork
1402 // mutates the original list
1403 for _, record := range needUpdate {
1404 if _, err := dc.user.updateNetwork(&record); err != nil {
1405 dc.logger.Printf("failed to update network realname: %v", err)
1406 storeErr = err
1407 }
1408 }
1409 if storeErr != nil {
1410 return ircError{&irc.Message{
1411 Command: "FAIL",
1412 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1413 }}
1414 }
1415
1416 if dc.upstream() == nil && dc.caps["setname"] {
1417 dc.SendMessage(&irc.Message{
1418 Prefix: dc.prefix(),
1419 Command: "SETNAME",
1420 Params: []string{realname},
1421 })
1422 }
1423 case "JOIN":
1424 var namesStr string
1425 if err := parseMessageParams(msg, &namesStr); err != nil {
1426 return err
1427 }
1428
1429 var keys []string
1430 if len(msg.Params) > 1 {
1431 keys = strings.Split(msg.Params[1], ",")
1432 }
1433
1434 for i, name := range strings.Split(namesStr, ",") {
1435 uc, upstreamName, err := dc.unmarshalEntity(name)
1436 if err != nil {
1437 return err
1438 }
1439
1440 var key string
1441 if len(keys) > i {
1442 key = keys[i]
1443 }
1444
1445 if !uc.isChannel(upstreamName) {
1446 dc.SendMessage(&irc.Message{
1447 Prefix: dc.srv.prefix(),
1448 Command: irc.ERR_NOSUCHCHANNEL,
1449 Params: []string{name, "Not a channel name"},
1450 })
1451 continue
1452 }
1453
1454 params := []string{upstreamName}
1455 if key != "" {
1456 params = append(params, key)
1457 }
1458 uc.SendMessageLabeled(dc.id, &irc.Message{
1459 Command: "JOIN",
1460 Params: params,
1461 })
1462
1463 ch := uc.network.channels.Value(upstreamName)
1464 if ch != nil {
1465 // Don't clear the channel key if there's one set
1466 // TODO: add a way to unset the channel key
1467 if key != "" {
1468 ch.Key = key
1469 }
1470 uc.network.attach(ch)
1471 } else {
1472 ch = &Channel{
1473 Name: upstreamName,
1474 Key: key,
1475 }
1476 uc.network.channels.SetValue(upstreamName, ch)
1477 }
1478 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1479 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1480 }
1481 }
1482 case "PART":
1483 var namesStr string
1484 if err := parseMessageParams(msg, &namesStr); err != nil {
1485 return err
1486 }
1487
1488 var reason string
1489 if len(msg.Params) > 1 {
1490 reason = msg.Params[1]
1491 }
1492
1493 for _, name := range strings.Split(namesStr, ",") {
1494 uc, upstreamName, err := dc.unmarshalEntity(name)
1495 if err != nil {
1496 return err
1497 }
1498
1499 if strings.EqualFold(reason, "detach") {
1500 ch := uc.network.channels.Value(upstreamName)
1501 if ch != nil {
1502 uc.network.detach(ch)
1503 } else {
1504 ch = &Channel{
1505 Name: name,
1506 Detached: true,
1507 }
1508 uc.network.channels.SetValue(upstreamName, ch)
1509 }
1510 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1511 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1512 }
1513 } else {
1514 params := []string{upstreamName}
1515 if reason != "" {
1516 params = append(params, reason)
1517 }
1518 uc.SendMessageLabeled(dc.id, &irc.Message{
1519 Command: "PART",
1520 Params: params,
1521 })
1522
1523 if err := uc.network.deleteChannel(upstreamName); err != nil {
1524 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1525 }
1526 }
1527 }
1528 case "KICK":
1529 var channelStr, userStr string
1530 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1531 return err
1532 }
1533
1534 channels := strings.Split(channelStr, ",")
1535 users := strings.Split(userStr, ",")
1536
1537 var reason string
1538 if len(msg.Params) > 2 {
1539 reason = msg.Params[2]
1540 }
1541
1542 if len(channels) != 1 && len(channels) != len(users) {
1543 return ircError{&irc.Message{
1544 Command: irc.ERR_BADCHANMASK,
1545 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1546 }}
1547 }
1548
1549 for i, user := range users {
1550 var channel string
1551 if len(channels) == 1 {
1552 channel = channels[0]
1553 } else {
1554 channel = channels[i]
1555 }
1556
1557 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1558 if err != nil {
1559 return err
1560 }
1561
1562 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1563 if err != nil {
1564 return err
1565 }
1566
1567 if ucChannel != ucUser {
1568 return ircError{&irc.Message{
1569 Command: irc.ERR_USERNOTINCHANNEL,
1570 Params: []string{dc.nick, user, channel, "They are on another network"},
1571 }}
1572 }
1573 uc := ucChannel
1574
1575 params := []string{upstreamChannel, upstreamUser}
1576 if reason != "" {
1577 params = append(params, reason)
1578 }
1579 uc.SendMessageLabeled(dc.id, &irc.Message{
1580 Command: "KICK",
1581 Params: params,
1582 })
1583 }
1584 case "MODE":
1585 var name string
1586 if err := parseMessageParams(msg, &name); err != nil {
1587 return err
1588 }
1589
1590 var modeStr string
1591 if len(msg.Params) > 1 {
1592 modeStr = msg.Params[1]
1593 }
1594
1595 if casemapASCII(name) == dc.nickCM {
1596 if modeStr != "" {
1597 dc.forEachUpstream(func(uc *upstreamConn) {
1598 uc.SendMessageLabeled(dc.id, &irc.Message{
1599 Command: "MODE",
1600 Params: []string{uc.nick, modeStr},
1601 })
1602 })
1603 } else {
1604 // TODO: only do this in multi-upstream mode
1605 dc.SendMessage(&irc.Message{
1606 Prefix: dc.srv.prefix(),
1607 Command: irc.RPL_UMODEIS,
1608 Params: []string{dc.nick, ""}, // TODO
1609 })
1610 }
1611 return nil
1612 }
1613
1614 uc, upstreamName, err := dc.unmarshalEntity(name)
1615 if err != nil {
1616 return err
1617 }
1618
1619 if !uc.isChannel(upstreamName) {
1620 return ircError{&irc.Message{
1621 Command: irc.ERR_USERSDONTMATCH,
1622 Params: []string{dc.nick, "Cannot change mode for other users"},
1623 }}
1624 }
1625
1626 if modeStr != "" {
1627 params := []string{upstreamName, modeStr}
1628 params = append(params, msg.Params[2:]...)
1629 uc.SendMessageLabeled(dc.id, &irc.Message{
1630 Command: "MODE",
1631 Params: params,
1632 })
1633 } else {
1634 ch := uc.channels.Value(upstreamName)
1635 if ch == nil {
1636 return ircError{&irc.Message{
1637 Command: irc.ERR_NOSUCHCHANNEL,
1638 Params: []string{dc.nick, name, "No such channel"},
1639 }}
1640 }
1641
1642 if ch.modes == nil {
1643 // we haven't received the initial RPL_CHANNELMODEIS yet
1644 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1645 return nil
1646 }
1647
1648 modeStr, modeParams := ch.modes.Format()
1649 params := []string{dc.nick, name, modeStr}
1650 params = append(params, modeParams...)
1651
1652 dc.SendMessage(&irc.Message{
1653 Prefix: dc.srv.prefix(),
1654 Command: irc.RPL_CHANNELMODEIS,
1655 Params: params,
1656 })
1657 if ch.creationTime != "" {
1658 dc.SendMessage(&irc.Message{
1659 Prefix: dc.srv.prefix(),
1660 Command: rpl_creationtime,
1661 Params: []string{dc.nick, name, ch.creationTime},
1662 })
1663 }
1664 }
1665 case "TOPIC":
1666 var channel string
1667 if err := parseMessageParams(msg, &channel); err != nil {
1668 return err
1669 }
1670
1671 uc, upstreamName, err := dc.unmarshalEntity(channel)
1672 if err != nil {
1673 return err
1674 }
1675
1676 if len(msg.Params) > 1 { // setting topic
1677 topic := msg.Params[1]
1678 uc.SendMessageLabeled(dc.id, &irc.Message{
1679 Command: "TOPIC",
1680 Params: []string{upstreamName, topic},
1681 })
1682 } else { // getting topic
1683 ch := uc.channels.Value(upstreamName)
1684 if ch == nil {
1685 return ircError{&irc.Message{
1686 Command: irc.ERR_NOSUCHCHANNEL,
1687 Params: []string{dc.nick, upstreamName, "No such channel"},
1688 }}
1689 }
1690 sendTopic(dc, ch)
1691 }
1692 case "LIST":
1693 // TODO: support ELIST when supported by all upstreams
1694
1695 pl := pendingLIST{
1696 downstreamID: dc.id,
1697 pendingCommands: make(map[int64]*irc.Message),
1698 }
1699 var upstream *upstreamConn
1700 var upstreamChannels map[int64][]string
1701 if len(msg.Params) > 0 {
1702 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1703 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1704 upstream = uc
1705 } else {
1706 upstreamChannels = make(map[int64][]string)
1707 channels := strings.Split(msg.Params[0], ",")
1708 for _, channel := range channels {
1709 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1710 if err != nil {
1711 return err
1712 }
1713 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1714 }
1715 }
1716 }
1717
1718 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1719 dc.forEachUpstream(func(uc *upstreamConn) {
1720 if upstream != nil && upstream != uc {
1721 return
1722 }
1723 var params []string
1724 if upstreamChannels != nil {
1725 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1726 params = []string{strings.Join(channels, ",")}
1727 } else {
1728 return
1729 }
1730 }
1731 pl.pendingCommands[uc.network.ID] = &irc.Message{
1732 Command: "LIST",
1733 Params: params,
1734 }
1735 uc.trySendLIST(dc.id)
1736 })
1737 case "NAMES":
1738 if len(msg.Params) == 0 {
1739 dc.SendMessage(&irc.Message{
1740 Prefix: dc.srv.prefix(),
1741 Command: irc.RPL_ENDOFNAMES,
1742 Params: []string{dc.nick, "*", "End of /NAMES list"},
1743 })
1744 return nil
1745 }
1746
1747 channels := strings.Split(msg.Params[0], ",")
1748 for _, channel := range channels {
1749 uc, upstreamName, err := dc.unmarshalEntity(channel)
1750 if err != nil {
1751 return err
1752 }
1753
1754 ch := uc.channels.Value(upstreamName)
1755 if ch != nil {
1756 sendNames(dc, ch)
1757 } else {
1758 // NAMES on a channel we have not joined, ask upstream
1759 uc.SendMessageLabeled(dc.id, &irc.Message{
1760 Command: "NAMES",
1761 Params: []string{upstreamName},
1762 })
1763 }
1764 }
1765 case "WHO":
1766 if len(msg.Params) == 0 {
1767 // TODO: support WHO without parameters
1768 dc.SendMessage(&irc.Message{
1769 Prefix: dc.srv.prefix(),
1770 Command: irc.RPL_ENDOFWHO,
1771 Params: []string{dc.nick, "*", "End of /WHO list"},
1772 })
1773 return nil
1774 }
1775
1776 // TODO: support WHO masks
1777 entity := msg.Params[0]
1778 entityCM := casemapASCII(entity)
1779
1780 if dc.network == nil && entityCM == dc.nickCM {
1781 // TODO: support AWAY (H/G) in self WHO reply
1782 dc.SendMessage(&irc.Message{
1783 Prefix: dc.srv.prefix(),
1784 Command: irc.RPL_WHOREPLY,
1785 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1786 })
1787 dc.SendMessage(&irc.Message{
1788 Prefix: dc.srv.prefix(),
1789 Command: irc.RPL_ENDOFWHO,
1790 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1791 })
1792 return nil
1793 }
1794 if entityCM == serviceNickCM {
1795 dc.SendMessage(&irc.Message{
1796 Prefix: dc.srv.prefix(),
1797 Command: irc.RPL_WHOREPLY,
1798 Params: []string{serviceNick, "*", servicePrefix.User, servicePrefix.Host, dc.srv.Hostname, serviceNick, "H", "0 " + serviceRealname},
1799 })
1800 dc.SendMessage(&irc.Message{
1801 Prefix: dc.srv.prefix(),
1802 Command: irc.RPL_ENDOFWHO,
1803 Params: []string{dc.nick, serviceNick, "End of /WHO list"},
1804 })
1805 return nil
1806 }
1807
1808 uc, upstreamName, err := dc.unmarshalEntity(entity)
1809 if err != nil {
1810 return err
1811 }
1812
1813 var params []string
1814 if len(msg.Params) == 2 {
1815 params = []string{upstreamName, msg.Params[1]}
1816 } else {
1817 params = []string{upstreamName}
1818 }
1819
1820 uc.SendMessageLabeled(dc.id, &irc.Message{
1821 Command: "WHO",
1822 Params: params,
1823 })
1824 case "WHOIS":
1825 if len(msg.Params) == 0 {
1826 return ircError{&irc.Message{
1827 Command: irc.ERR_NONICKNAMEGIVEN,
1828 Params: []string{dc.nick, "No nickname given"},
1829 }}
1830 }
1831
1832 var target, mask string
1833 if len(msg.Params) == 1 {
1834 target = ""
1835 mask = msg.Params[0]
1836 } else {
1837 target = msg.Params[0]
1838 mask = msg.Params[1]
1839 }
1840 // TODO: support multiple WHOIS users
1841 if i := strings.IndexByte(mask, ','); i >= 0 {
1842 mask = mask[:i]
1843 }
1844
1845 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
1846 dc.SendMessage(&irc.Message{
1847 Prefix: dc.srv.prefix(),
1848 Command: irc.RPL_WHOISUSER,
1849 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1850 })
1851 dc.SendMessage(&irc.Message{
1852 Prefix: dc.srv.prefix(),
1853 Command: irc.RPL_WHOISSERVER,
1854 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1855 })
1856 dc.SendMessage(&irc.Message{
1857 Prefix: dc.srv.prefix(),
1858 Command: irc.RPL_ENDOFWHOIS,
1859 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1860 })
1861 return nil
1862 }
1863
1864 // TODO: support WHOIS masks
1865 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1866 if err != nil {
1867 return err
1868 }
1869
1870 var params []string
1871 if target != "" {
1872 if target == mask { // WHOIS nick nick
1873 params = []string{upstreamNick, upstreamNick}
1874 } else {
1875 params = []string{target, upstreamNick}
1876 }
1877 } else {
1878 params = []string{upstreamNick}
1879 }
1880
1881 uc.SendMessageLabeled(dc.id, &irc.Message{
1882 Command: "WHOIS",
1883 Params: params,
1884 })
1885 case "PRIVMSG":
1886 var targetsStr, text string
1887 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1888 return err
1889 }
1890 tags := copyClientTags(msg.Tags)
1891
1892 for _, name := range strings.Split(targetsStr, ",") {
1893 if dc.network == nil && casemapASCII(name) == dc.nickCM {
1894 dc.SendMessage(msg)
1895 continue
1896 }
1897
1898 if casemapASCII(name) == serviceNickCM {
1899 if dc.caps["echo-message"] {
1900 echoTags := tags.Copy()
1901 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1902 dc.SendMessage(&irc.Message{
1903 Tags: echoTags,
1904 Prefix: dc.prefix(),
1905 Command: "PRIVMSG",
1906 Params: []string{name, text},
1907 })
1908 }
1909 handleServicePRIVMSG(dc, text)
1910 continue
1911 }
1912
1913 uc, upstreamName, err := dc.unmarshalEntity(name)
1914 if err != nil {
1915 return err
1916 }
1917
1918 if uc.network.casemap(upstreamName) == "nickserv" {
1919 dc.handleNickServPRIVMSG(uc, text)
1920 }
1921
1922 unmarshaledText := text
1923 if uc.isChannel(upstreamName) {
1924 unmarshaledText = dc.unmarshalText(uc, text)
1925 }
1926 uc.SendMessageLabeled(dc.id, &irc.Message{
1927 Tags: tags,
1928 Command: "PRIVMSG",
1929 Params: []string{upstreamName, unmarshaledText},
1930 })
1931
1932 echoTags := tags.Copy()
1933 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1934 echoMsg := &irc.Message{
1935 Tags: echoTags,
1936 Prefix: &irc.Prefix{
1937 Name: uc.nick,
1938 User: uc.username,
1939 },
1940 Command: "PRIVMSG",
1941 Params: []string{upstreamName, text},
1942 }
1943 uc.produce(upstreamName, echoMsg, dc)
1944
1945 uc.updateChannelAutoDetach(upstreamName)
1946 }
1947 case "NOTICE":
1948 var targetsStr, text string
1949 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1950 return err
1951 }
1952 tags := copyClientTags(msg.Tags)
1953
1954 for _, name := range strings.Split(targetsStr, ",") {
1955 uc, upstreamName, err := dc.unmarshalEntity(name)
1956 if err != nil {
1957 return err
1958 }
1959
1960 unmarshaledText := text
1961 if uc.isChannel(upstreamName) {
1962 unmarshaledText = dc.unmarshalText(uc, text)
1963 }
1964 uc.SendMessageLabeled(dc.id, &irc.Message{
1965 Tags: tags,
1966 Command: "NOTICE",
1967 Params: []string{upstreamName, unmarshaledText},
1968 })
1969
1970 uc.updateChannelAutoDetach(upstreamName)
1971 }
1972 case "TAGMSG":
1973 var targetsStr string
1974 if err := parseMessageParams(msg, &targetsStr); err != nil {
1975 return err
1976 }
1977 tags := copyClientTags(msg.Tags)
1978
1979 for _, name := range strings.Split(targetsStr, ",") {
1980 uc, upstreamName, err := dc.unmarshalEntity(name)
1981 if err != nil {
1982 return err
1983 }
1984 if _, ok := uc.caps["message-tags"]; !ok {
1985 continue
1986 }
1987
1988 uc.SendMessageLabeled(dc.id, &irc.Message{
1989 Tags: tags,
1990 Command: "TAGMSG",
1991 Params: []string{upstreamName},
1992 })
1993
1994 uc.updateChannelAutoDetach(upstreamName)
1995 }
1996 case "INVITE":
1997 var user, channel string
1998 if err := parseMessageParams(msg, &user, &channel); err != nil {
1999 return err
2000 }
2001
2002 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2003 if err != nil {
2004 return err
2005 }
2006
2007 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2008 if err != nil {
2009 return err
2010 }
2011
2012 if ucChannel != ucUser {
2013 return ircError{&irc.Message{
2014 Command: irc.ERR_USERNOTINCHANNEL,
2015 Params: []string{dc.nick, user, channel, "They are on another network"},
2016 }}
2017 }
2018 uc := ucChannel
2019
2020 uc.SendMessageLabeled(dc.id, &irc.Message{
2021 Command: "INVITE",
2022 Params: []string{upstreamUser, upstreamChannel},
2023 })
2024 case "CHATHISTORY":
2025 var subcommand string
2026 if err := parseMessageParams(msg, &subcommand); err != nil {
2027 return err
2028 }
2029 var target, limitStr string
2030 var boundsStr [2]string
2031 switch subcommand {
2032 case "AFTER", "BEFORE":
2033 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2034 return err
2035 }
2036 case "BETWEEN":
2037 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2038 return err
2039 }
2040 case "TARGETS":
2041 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2042 return err
2043 }
2044 default:
2045 // TODO: support LATEST, AROUND
2046 return ircError{&irc.Message{
2047 Command: "FAIL",
2048 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
2049 }}
2050 }
2051
2052 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2053 if !ok {
2054 return ircError{&irc.Message{
2055 Command: irc.ERR_UNKNOWNCOMMAND,
2056 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
2057 }}
2058 }
2059
2060 uc, entity, err := dc.unmarshalEntity(target)
2061 if err != nil {
2062 return err
2063 }
2064 entity = uc.network.casemap(entity)
2065
2066 // TODO: support msgid criteria
2067 var bounds [2]time.Time
2068 bounds[0] = parseChatHistoryBound(boundsStr[0])
2069 if bounds[0].IsZero() {
2070 return ircError{&irc.Message{
2071 Command: "FAIL",
2072 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
2073 }}
2074 }
2075
2076 if boundsStr[1] != "" {
2077 bounds[1] = parseChatHistoryBound(boundsStr[1])
2078 if bounds[1].IsZero() {
2079 return ircError{&irc.Message{
2080 Command: "FAIL",
2081 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2082 }}
2083 }
2084 }
2085
2086 limit, err := strconv.Atoi(limitStr)
2087 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
2088 return ircError{&irc.Message{
2089 Command: "FAIL",
2090 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
2091 }}
2092 }
2093
2094 var history []*irc.Message
2095 switch subcommand {
2096 case "BEFORE":
2097 history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], time.Time{}, limit)
2098 case "AFTER":
2099 history, err = store.LoadAfterTime(uc.network, entity, bounds[0], time.Now(), limit)
2100 case "BETWEEN":
2101 if bounds[0].Before(bounds[1]) {
2102 history, err = store.LoadAfterTime(uc.network, entity, bounds[0], bounds[1], limit)
2103 } else {
2104 history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], bounds[1], limit)
2105 }
2106 case "TARGETS":
2107 // TODO: support TARGETS in multi-upstream mode
2108 targets, err := store.ListTargets(uc.network, bounds[0], bounds[1], limit)
2109 if err != nil {
2110 dc.logger.Printf("failed fetching targets for chathistory: %v", target, err)
2111 return ircError{&irc.Message{
2112 Command: "FAIL",
2113 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2114 }}
2115 }
2116
2117 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2118 for _, target := range targets {
2119 if ch := uc.network.channels.Value(target.Name); ch != nil && ch.Detached {
2120 continue
2121 }
2122
2123 dc.SendMessage(&irc.Message{
2124 Tags: irc.Tags{"batch": batchRef},
2125 Prefix: dc.srv.prefix(),
2126 Command: "CHATHISTORY",
2127 Params: []string{"TARGETS", target.Name, target.LatestMessage.UTC().Format(serverTimeLayout)},
2128 })
2129 }
2130 })
2131
2132 return nil
2133 }
2134 if err != nil {
2135 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
2136 return newChatHistoryError(subcommand, target)
2137 }
2138
2139 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2140 for _, msg := range history {
2141 msg.Tags["batch"] = batchRef
2142 dc.SendMessage(dc.marshalMessage(msg, uc.network))
2143 }
2144 })
2145 case "BOUNCER":
2146 var subcommand string
2147 if err := parseMessageParams(msg, &subcommand); err != nil {
2148 return err
2149 }
2150
2151 switch strings.ToUpper(subcommand) {
2152 case "LISTNETWORKS":
2153 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
2154 dc.user.forEachNetwork(func(network *network) {
2155 idStr := fmt.Sprintf("%v", network.ID)
2156 attrs := getNetworkAttrs(network)
2157 dc.SendMessage(&irc.Message{
2158 Tags: irc.Tags{"batch": batchRef},
2159 Prefix: dc.srv.prefix(),
2160 Command: "BOUNCER",
2161 Params: []string{"NETWORK", idStr, attrs.String()},
2162 })
2163 })
2164 })
2165 case "ADDNETWORK":
2166 var attrsStr string
2167 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2168 return err
2169 }
2170 attrs := irc.ParseTags(attrsStr)
2171
2172 host, ok := attrs.GetTag("host")
2173 if !ok {
2174 return ircError{&irc.Message{
2175 Command: "FAIL",
2176 Params: []string{"BOUNCER", "NEED_ATTRIBUTE", subcommand, "host", "Missing required host attribute"},
2177 }}
2178 }
2179
2180 addr := host
2181 if port, ok := attrs.GetTag("port"); ok {
2182 addr += ":" + port
2183 }
2184
2185 if tlsStr, ok := attrs.GetTag("tls"); ok && tlsStr == "0" {
2186 addr = "irc+insecure://" + tlsStr
2187 }
2188
2189 nick, ok := attrs.GetTag("nickname")
2190 if !ok {
2191 nick = dc.nick
2192 }
2193
2194 username, _ := attrs.GetTag("username")
2195 realname, _ := attrs.GetTag("realname")
2196 pass, _ := attrs.GetTag("pass")
2197
2198 // TODO: reject unknown attributes
2199
2200 record := &Network{
2201 Addr: addr,
2202 Nick: nick,
2203 Username: username,
2204 Realname: realname,
2205 Pass: pass,
2206 Enabled: true,
2207 }
2208 network, err := dc.user.createNetwork(record)
2209 if err != nil {
2210 return ircError{&irc.Message{
2211 Command: "FAIL",
2212 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2213 }}
2214 }
2215
2216 dc.SendMessage(&irc.Message{
2217 Prefix: dc.srv.prefix(),
2218 Command: "BOUNCER",
2219 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2220 })
2221 case "CHANGENETWORK":
2222 var idStr, attrsStr string
2223 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2224 return err
2225 }
2226 id, err := parseBouncerNetID(subcommand, idStr)
2227 if err != nil {
2228 return err
2229 }
2230 attrs := irc.ParseTags(attrsStr)
2231
2232 net := dc.user.getNetworkByID(id)
2233 if net == nil {
2234 return ircError{&irc.Message{
2235 Command: "FAIL",
2236 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2237 }}
2238 }
2239
2240 record := net.Network // copy network record because we'll mutate it
2241 for k, v := range attrs {
2242 s := string(v)
2243 switch k {
2244 // TODO: host, port, tls
2245 case "nickname":
2246 record.Nick = s
2247 case "username":
2248 record.Username = s
2249 case "realname":
2250 record.Realname = s
2251 case "pass":
2252 record.Pass = s
2253 default:
2254 return ircError{&irc.Message{
2255 Command: "FAIL",
2256 Params: []string{"BOUNCER", "UNKNOWN_ATTRIBUTE", subcommand, k, "Unknown attribute"},
2257 }}
2258 }
2259 }
2260
2261 _, err = dc.user.updateNetwork(&record)
2262 if err != nil {
2263 return ircError{&irc.Message{
2264 Command: "FAIL",
2265 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2266 }}
2267 }
2268
2269 dc.SendMessage(&irc.Message{
2270 Prefix: dc.srv.prefix(),
2271 Command: "BOUNCER",
2272 Params: []string{"CHANGENETWORK", idStr},
2273 })
2274 case "DELNETWORK":
2275 var idStr string
2276 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2277 return err
2278 }
2279 id, err := parseBouncerNetID(subcommand, idStr)
2280 if err != nil {
2281 return err
2282 }
2283
2284 net := dc.user.getNetworkByID(id)
2285 if net == nil {
2286 return ircError{&irc.Message{
2287 Command: "FAIL",
2288 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2289 }}
2290 }
2291
2292 if err := dc.user.deleteNetwork(net.ID); err != nil {
2293 return err
2294 }
2295
2296 dc.SendMessage(&irc.Message{
2297 Prefix: dc.srv.prefix(),
2298 Command: "BOUNCER",
2299 Params: []string{"DELNETWORK", idStr},
2300 })
2301 default:
2302 return ircError{&irc.Message{
2303 Command: "FAIL",
2304 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2305 }}
2306 }
2307 default:
2308 dc.logger.Printf("unhandled message: %v", msg)
2309
2310 // Only forward unknown commands in single-upstream mode
2311 uc := dc.upstream()
2312 if uc == nil {
2313 return newUnknownCommandError(msg.Command)
2314 }
2315
2316 uc.SendMessageLabeled(dc.id, msg)
2317 }
2318 return nil
2319}
2320
2321func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
2322 username, password, ok := parseNickServCredentials(text, uc.nick)
2323 if !ok {
2324 return
2325 }
2326
2327 // User may have e.g. EXTERNAL mechanism configured. We do not want to
2328 // automatically erase the key pair or any other credentials.
2329 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" {
2330 return
2331 }
2332
2333 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
2334 n := uc.network
2335 n.SASL.Mechanism = "PLAIN"
2336 n.SASL.Plain.Username = username
2337 n.SASL.Plain.Password = password
2338 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
2339 dc.logger.Printf("failed to save NickServ credentials: %v", err)
2340 }
2341}
2342
2343func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
2344 fields := strings.Fields(text)
2345 if len(fields) < 2 {
2346 return "", "", false
2347 }
2348 cmd := strings.ToUpper(fields[0])
2349 params := fields[1:]
2350 switch cmd {
2351 case "REGISTER":
2352 username = nick
2353 password = params[0]
2354 case "IDENTIFY":
2355 if len(params) == 1 {
2356 username = nick
2357 password = params[0]
2358 } else {
2359 username = params[0]
2360 password = params[1]
2361 }
2362 case "SET":
2363 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
2364 username = nick
2365 password = params[1]
2366 }
2367 default:
2368 return "", "", false
2369 }
2370 return username, password, true
2371}
Note: See TracBrowser for help on using the repository browser.