source: code/trunk/downstream.go@ 559

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

Add support for account-tag

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