source: code/trunk/downstream.go@ 509

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

Relay detached channel backlog as BouncerServ NOTICE if necessary

Instead of ignoring detached channels wehn replaying backlog,
process them as usual and relay messages as BouncerServ NOTICEs
if necessary. Advance the delivery receipts as if the channel was
attached.

Closes: https://todo.sr.ht/~emersion/soju/98

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