source: code/trunk/downstream.go@ 507

Last change on this file since 507 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
RevLine 
[98]1package soju
[13]2
3import (
[91]4 "crypto/tls"
[112]5 "encoding/base64"
[13]6 "fmt"
7 "io"
8 "net"
[108]9 "strconv"
[39]10 "strings"
[91]11 "time"
[13]12
[112]13 "github.com/emersion/go-sasl"
[85]14 "golang.org/x/crypto/bcrypt"
[13]15 "gopkg.in/irc.v3"
16)
17
18type ircError struct {
19 Message *irc.Message
20}
21
[85]22func (err ircError) Error() string {
23 return err.Message.String()
24}
25
[13]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
[319]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
[85]55var errAuthFailed = ircError{&irc.Message{
56 Command: irc.ERR_PASSWDMISMATCH,
57 Params: []string{"*", "Invalid username or password"},
58}}
[13]59
[411]60// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
61// '*' and '?' break masks
62const illegalNickChars = " :@!*?"
[404]63
[275]64// permanentDownstreamCaps is the list of always-supported downstream
65// capabilities.
66var permanentDownstreamCaps = map[string]string{
[448]67 "batch": "",
68 "cap-notify": "",
69 "echo-message": "",
70 "invite-notify": "",
71 "message-tags": "",
72 "sasl": "PLAIN",
73 "server-time": "",
[275]74}
75
[292]76// needAllDownstreamCaps is the list of downstream capabilities that
77// require support from all upstreams to be enabled
78var needAllDownstreamCaps = map[string]string{
[419]79 "away-notify": "",
80 "extended-join": "",
81 "multi-prefix": "",
[292]82}
83
[463]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
[13]111type downstreamConn struct {
[210]112 conn
[22]113
[210]114 id uint64
115
[100]116 registered bool
117 user *user
118 nick string
[478]119 nickCM string
[100]120 rawUsername string
[168]121 networkName string
[183]122 clientName string
[100]123 realname string
[141]124 hostname string
[100]125 password string // empty after authentication
126 network *network // can be nil
[105]127
[108]128 negociatingCaps bool
129 capVersion int
[275]130 supportedCaps map[string]string
[236]131 caps map[string]bool
[108]132
[112]133 saslServer sasl.Server
[13]134}
135
[347]136func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
137 remoteAddr := ic.RemoteAddr().String()
[323]138 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
[398]139 options := connOptions{Logger: logger}
[55]140 dc := &downstreamConn{
[398]141 conn: *newConn(srv, ic, &options),
[276]142 id: id,
[275]143 supportedCaps: make(map[string]string),
[276]144 caps: make(map[string]bool),
[22]145 }
[323]146 dc.hostname = remoteAddr
[141]147 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
148 dc.hostname = host
149 }
[275]150 for k, v := range permanentDownstreamCaps {
151 dc.supportedCaps[k] = v
152 }
[319]153 if srv.LogPath != "" {
154 dc.supportedCaps["draft/chathistory"] = ""
155 }
[55]156 return dc
[22]157}
158
[55]159func (dc *downstreamConn) prefix() *irc.Prefix {
[27]160 return &irc.Prefix{
[55]161 Name: dc.nick,
[184]162 User: dc.user.Username,
[141]163 Host: dc.hostname,
[27]164 }
165}
166
[90]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
[73]175func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
176 dc.user.forEachUpstream(func(uc *upstreamConn) {
[77]177 if dc.network != nil && uc.network != dc.network {
[73]178 return
179 }
180 f(uc)
181 })
182}
183
[89]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 }
[279]190 return dc.network.conn
[89]191}
192
[260]193func isOurNick(net *network, nick string) bool {
194 // TODO: this doesn't account for nick changes
195 if net.conn != nil {
[478]196 return net.casemap(nick) == net.conn.nickCM
[260]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.
[478]202 return net.casemap(nick) == net.casemap(net.Nick)
[260]203}
204
[249]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.
[260]210func (dc *downstreamConn) marshalEntity(net *network, name string) string {
[289]211 if isOurNick(net, name) {
212 return dc.nick
213 }
[478]214 name = partialCasemap(net.casemap, name)
[257]215 if dc.network != nil {
[260]216 if dc.network != net {
[258]217 panic("soju: tried to marshal an entity for another network")
218 }
[257]219 return name
[119]220 }
[260]221 return name + "/" + net.GetName()
[119]222}
223
[260]224func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
225 if isOurNick(net, prefix.Name) {
[257]226 return dc.prefix()
227 }
[478]228 prefix.Name = partialCasemap(net.casemap, prefix.Name)
[130]229 if dc.network != nil {
[260]230 if dc.network != net {
[258]231 panic("soju: tried to marshal a user prefix for another network")
232 }
[257]233 return prefix
[119]234 }
[257]235 return &irc.Prefix{
[260]236 Name: prefix.Name + "/" + net.GetName(),
[257]237 User: prefix.User,
238 Host: prefix.Host,
239 }
[119]240}
241
[249]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.
[127]246func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
[89]247 if uc := dc.upstream(); uc != nil {
248 return uc, name, nil
249 }
[464]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 }
[89]256
[127]257 var conn *upstreamConn
[119]258 if i := strings.LastIndexByte(name, '/'); i >= 0 {
[127]259 network := name[i+1:]
[119]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
[127]270 if conn == nil {
[73]271 return nil, "", ircError{&irc.Message{
272 Command: irc.ERR_NOSUCHCHANNEL,
[464]273 Params: []string{name, "Missing network suffix in channel name"},
[73]274 }}
[69]275 }
[127]276 return conn, name, nil
[69]277}
278
[268]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
[165]287func (dc *downstreamConn) readMessages(ch chan<- event) error {
[22]288 for {
[210]289 msg, err := dc.ReadMessage()
[22]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
[165]296 ch <- eventDownstreamMessage{msg, dc}
[22]297 }
298
[45]299 return nil
[22]300}
301
[230]302// SendMessage sends an outgoing message.
303//
304// This can only called from the user goroutine.
[55]305func (dc *downstreamConn) SendMessage(msg *irc.Message) {
[230]306 if !dc.caps["message-tags"] {
[303]307 if msg.Command == "TAGMSG" {
308 return
309 }
[216]310 msg = msg.Copy()
311 for name := range msg.Tags {
312 supported := false
313 switch name {
314 case "time":
[230]315 supported = dc.caps["server-time"]
[216]316 }
317 if !supported {
318 delete(msg.Tags, name)
319 }
320 }
321 }
[419]322 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
323 msg.Params = msg.Params[:1]
324 }
[216]325
[210]326 dc.conn.SendMessage(msg)
[54]327}
328
[428]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) {
[488]353 netID, entity, err := parseMsgID(id, nil)
[428]354 if err != nil {
355 dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
356 return
357 }
358
[440]359 network := dc.user.getNetworkByID(netID)
[428]360 if network == nil {
361 return
362 }
363
[485]364 network.delivered.StoreID(entity, dc.clientName, id)
[428]365}
366
367func (dc *downstreamConn) sendPing(msgID string) {
[488]368 token := "soju-msgid-" + msgID
[428]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 }
[488]380 msgID := strings.TrimPrefix(token, "soju-msgid-")
[428]381 dc.ackMsgID(msgID)
382}
383
[245]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
[293]386// messages that may appear in logs are supported, except MODE.
[261]387func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
[227]388 msg = msg.Copy()
[261]389 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
[245]390
[227]391 switch msg.Command {
[303]392 case "PRIVMSG", "NOTICE", "TAGMSG":
[261]393 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]394 case "NICK":
395 // Nick change for another user
[261]396 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]397 case "JOIN", "PART":
[261]398 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]399 case "KICK":
[261]400 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
401 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
[245]402 case "TOPIC":
[261]403 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]404 case "QUIT":
[262]405 // This space is intentionally left blank
[227]406 default:
407 panic(fmt.Sprintf("unexpected %q message", msg.Command))
408 }
409
[245]410 return msg
[227]411}
412
[55]413func (dc *downstreamConn) handleMessage(msg *irc.Message) error {
[13]414 switch msg.Command {
[28]415 case "QUIT":
[55]416 return dc.Close()
[13]417 default:
[55]418 if dc.registered {
419 return dc.handleMessageRegistered(msg)
[13]420 } else {
[55]421 return dc.handleMessageUnregistered(msg)
[13]422 }
423 }
424}
425
[55]426func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
[13]427 switch msg.Command {
428 case "NICK":
[117]429 var nick string
430 if err := parseMessageParams(msg, &nick); err != nil {
[43]431 return err
[13]432 }
[404]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 }
[478]439 nickCM := casemapASCII(nick)
440 if nickCM == serviceNickCM {
[117]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
[478]447 dc.nickCM = nickCM
[13]448 case "USER":
[117]449 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
[43]450 return err
[13]451 }
[85]452 case "PASS":
453 if err := parseMessageParams(msg, &dc.password); err != nil {
454 return err
455 }
[108]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 }
[112]464 case "AUTHENTICATE":
[230]465 if !dc.caps["sasl"] {
[112]466 return ircError{&irc.Message{
[125]467 Command: irc.ERR_SASLFAIL,
[112]468 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
469 }}
470 }
471 if len(msg.Params) == 0 {
472 return ircError{&irc.Message{
[125]473 Command: irc.ERR_SASLFAIL,
[112]474 Params: []string{"*", "Missing AUTHENTICATE argument"},
475 }}
476 }
477 if dc.nick == "" {
478 return ircError{&irc.Message{
[125]479 Command: irc.ERR_SASLFAIL,
[112]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{
[125]494 Command: irc.ERR_SASLFAIL,
[112]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{
[125]501 Command: irc.ERR_SASLABORTED,
[112]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{
[125]513 Command: irc.ERR_SASLFAIL,
[112]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{
[125]524 Command: irc.ERR_SASLFAIL,
[112]525 Params: []string{"*", ircErr.Message.Params[1]},
526 }}
527 }
528 dc.SendMessage(&irc.Message{
529 Prefix: dc.srv.prefix(),
[125]530 Command: irc.ERR_SASLFAIL,
[112]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(),
[125]538 Command: irc.RPL_LOGGEDIN,
[306]539 Params: []string{dc.nick, dc.prefix().String(), dc.user.Username, "You are now logged in"},
[112]540 })
541 dc.SendMessage(&irc.Message{
542 Prefix: dc.srv.prefix(),
[125]543 Command: irc.RPL_SASLSUCCESS,
[112]544 Params: []string{dc.nick, "SASL authentication successful"},
545 })
546 } else {
547 challengeStr := "+"
[135]548 if len(challenge) > 0 {
[112]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 }
[13]559 default:
[55]560 dc.logger.Printf("unhandled message: %v", msg)
[13]561 return newUnknownCommandError(msg.Command)
562 }
[108]563 if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
[55]564 return dc.register()
[13]565 }
566 return nil
567}
568
[108]569func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
[111]570 cmd = strings.ToUpper(cmd)
571
[108]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 }
[437]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 }
[108]593
[275]594 caps := make([]string, 0, len(dc.supportedCaps))
595 for k, v := range dc.supportedCaps {
596 if dc.capVersion >= 302 && v != "" {
[276]597 caps = append(caps, k+"="+v)
[275]598 } else {
599 caps = append(caps, k)
600 }
[112]601 }
[108]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
[275]610 if dc.capVersion >= 302 {
611 // CAP version 302 implicitly enables cap-notify
612 dc.caps["cap-notify"] = true
613 }
614
[108]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
[275]638 // TODO: atomically ack/nak the whole capability set
[108]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
[275]648 if enable == dc.caps[name] {
[108]649 continue
650 }
651
[275]652 _, ok := dc.supportedCaps[name]
653 if !ok {
[108]654 ack = false
[275]655 break
[108]656 }
[275]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
[108]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
[275]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
[276]734func (dc *downstreamConn) updateSupportedCaps() {
[292]735 supportedCaps := make(map[string]bool)
736 for cap := range needAllDownstreamCaps {
737 supportedCaps[cap] = true
738 }
[276]739 dc.forEachUpstream(func(uc *upstreamConn) {
[292]740 for cap, supported := range supportedCaps {
741 supportedCaps[cap] = supported && uc.caps[cap]
742 }
[276]743 })
744
[292]745 for cap, supported := range supportedCaps {
746 if supported {
747 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
748 } else {
749 dc.unsetSupportedCap(cap)
750 }
[276]751 }
752}
753
[296]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
[478]762 dc.nickCM = casemapASCII(dc.nick)
[296]763 }
764}
765
[91]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
[183]775func unmarshalUsername(rawUsername string) (username, client, network string) {
[112]776 username = rawUsername
[183]777
778 i := strings.IndexAny(username, "/@")
779 j := strings.LastIndexAny(username, "/@")
780 if i >= 0 {
781 username = rawUsername[:i]
[73]782 }
[183]783 if j >= 0 {
[190]784 if rawUsername[j] == '@' {
785 client = rawUsername[j+1:]
786 } else {
787 network = rawUsername[j+1:]
788 }
[73]789 }
[183]790 if i >= 0 && j >= 0 && i < j {
[190]791 if rawUsername[i] == '@' {
792 client = rawUsername[i+1 : j]
793 } else {
794 network = rawUsername[i+1 : j]
795 }
[183]796 }
797
798 return username, client, network
[112]799}
[73]800
[168]801func (dc *downstreamConn) authenticate(username, password string) error {
[183]802 username, clientName, networkName := unmarshalUsername(username)
[168]803
[173]804 u, err := dc.srv.db.GetUser(username)
805 if err != nil {
[438]806 dc.logger.Printf("failed authentication for %q: user not found: %v", username, err)
[168]807 return errAuthFailed
808 }
809
[322]810 // Password auth disabled
811 if u.Password == "" {
812 return errAuthFailed
813 }
814
[173]815 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
[168]816 if err != nil {
[438]817 dc.logger.Printf("failed authentication for %q: wrong password: %v", username, err)
[168]818 return errAuthFailed
819 }
820
[173]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 }
[183]826 dc.clientName = clientName
[168]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
[183]844 if dc.clientName == "" && dc.networkName == "" {
845 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
[168]846 }
847
848 dc.registered = true
[184]849 dc.logger.Printf("registration complete for user %q", dc.user.Username)
[168]850 return nil
851}
852
853func (dc *downstreamConn) loadNetwork() error {
854 if dc.networkName == "" {
[112]855 return nil
856 }
[85]857
[168]858 network := dc.user.getNetwork(dc.networkName)
[112]859 if network == nil {
[168]860 addr := dc.networkName
[112]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,
[168]870 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
[112]871 }}
872 }
873
[354]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
[168]879 dc.logger.Printf("auto-saving network %q", dc.networkName)
[112]880 var err error
[120]881 network, err = dc.user.createNetwork(&Network{
[168]882 Addr: dc.networkName,
[354]883 Nick: nick,
[120]884 })
[112]885 if err != nil {
886 return err
887 }
888 }
889
890 dc.network = network
891 return nil
892}
893
[168]894func (dc *downstreamConn) welcome() error {
895 if dc.user == nil || !dc.registered {
896 panic("tried to welcome an unregistered connection")
[37]897 }
898
[168]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
[85]904 }
905
[446]906 isupport := []string{
907 fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit),
[478]908 "CASEMAPPING=ascii",
[446]909 }
910
[463]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 }
[447]923 }
924
[55]925 dc.SendMessage(&irc.Message{
926 Prefix: dc.srv.prefix(),
[13]927 Command: irc.RPL_WELCOME,
[98]928 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
[54]929 })
[55]930 dc.SendMessage(&irc.Message{
931 Prefix: dc.srv.prefix(),
[13]932 Command: irc.RPL_YOURHOST,
[55]933 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
[54]934 })
[55]935 dc.SendMessage(&irc.Message{
936 Prefix: dc.srv.prefix(),
[13]937 Command: irc.RPL_CREATED,
[55]938 Params: []string{dc.nick, "Who cares when the server was created?"},
[54]939 })
[55]940 dc.SendMessage(&irc.Message{
941 Prefix: dc.srv.prefix(),
[13]942 Command: irc.RPL_MYINFO,
[98]943 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
[54]944 })
[463]945 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
946 dc.SendMessage(msg)
947 }
[55]948 dc.SendMessage(&irc.Message{
[447]949 Prefix: dc.srv.prefix(),
[13]950 Command: irc.ERR_NOMOTD,
[55]951 Params: []string{dc.nick, "No MOTD"},
[54]952 })
[13]953
[296]954 dc.updateNick()
[437]955 dc.updateSupportedCaps()
[296]956
[73]957 dc.forEachUpstream(func(uc *upstreamConn) {
[478]958 for _, entry := range uc.channels.innerMap {
959 ch := entry.value.(*upstreamChannel)
[284]960 if !ch.complete {
961 continue
962 }
[478]963 record := uc.network.channels.Value(ch.Name)
964 if record != nil && record.Detached {
[284]965 continue
966 }
[132]967
[284]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)
[30]975 }
[143]976 })
[50]977
[143]978 dc.forEachNetwork(func(net *network) {
[496]979 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
980 return
981 }
982
[253]983 // Only send history if we're the first connected client with that name
984 // for the network
[482]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 {
[485]992 net.delivered.ForEachTarget(func(target string) {
[495]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)
[485]1008 })
[227]1009 }
[253]1010 })
[57]1011
[253]1012 return nil
1013}
[144]1014
[428]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
[495]1029func (dc *downstreamConn) sendTargetBacklog(net *network, target, msgID string) {
[423]1030 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
[319]1031 return
1032 }
[485]1033
[499]1034 ch := net.channels.Value(target)
1035
[452]1036 limit := 4000
[484]1037 targetCM := net.casemap(target)
[495]1038 history, err := dc.user.msgStore.LoadLatestID(net, targetCM, msgID, limit)
[452]1039 if err != nil {
[495]1040 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
[452]1041 return
1042 }
[253]1043
[452]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) {
[409]1055 continue
1056 }
[253]1057
[499]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))
[256]1067 }
[452]1068 }
[256]1069
[452]1070 if dc.caps["batch"] {
1071 dc.SendMessage(&irc.Message{
1072 Prefix: dc.srv.prefix(),
1073 Command: "BATCH",
1074 Params: []string{"-" + batchRef},
1075 })
[253]1076 }
[13]1077}
1078
[499]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
[103]1093func (dc *downstreamConn) runUntilRegistered() error {
1094 for !dc.registered {
[212]1095 msg, err := dc.ReadMessage()
[106]1096 if err != nil {
[103]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
[55]1112func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
[13]1113 switch msg.Command {
[111]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 }
[107]1122 case "PING":
[412]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,
[413]1133 Params: []string{dc.nick, destination, "No such server"},
[412]1134 }}
1135 }
[107]1136 dc.SendMessage(&irc.Message{
1137 Prefix: dc.srv.prefix(),
1138 Command: "PONG",
[412]1139 Params: []string{dc.srv.Hostname, source},
[107]1140 })
1141 return nil
[428]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)
[42]1148 case "USER":
[13]1149 return ircError{&irc.Message{
1150 Command: irc.ERR_ALREADYREGISTERED,
[55]1151 Params: []string{dc.nick, "You may not reregister"},
[13]1152 }}
[42]1153 case "NICK":
[429]1154 var rawNick string
1155 if err := parseMessageParams(msg, &rawNick); err != nil {
[90]1156 return err
1157 }
1158
[429]1159 nick := rawNick
[297]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
[404]1169 if strings.ContainsAny(nick, illegalNickChars) {
1170 return ircError{&irc.Message{
1171 Command: irc.ERR_ERRONEUSNICKNAME,
[430]1172 Params: []string{dc.nick, rawNick, "contains illegal characters"},
[404]1173 }}
1174 }
[478]1175 if casemapASCII(nick) == serviceNickCM {
[429]1176 return ircError{&irc.Message{
1177 Command: irc.ERR_NICKNAMEINUSE,
1178 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1179 }}
1180 }
[404]1181
[90]1182 var err error
1183 dc.forEachNetwork(func(n *network) {
[297]1184 if err != nil || (upstream != nil && upstream.network != n) {
[90]1185 return
1186 }
1187 n.Nick = nick
[421]1188 err = dc.srv.db.StoreNetwork(dc.user.ID, &n.Network)
[90]1189 })
1190 if err != nil {
1191 return err
1192 }
1193
[73]1194 dc.forEachUpstream(func(uc *upstreamConn) {
[297]1195 if upstream != nil && upstream != uc {
1196 return
1197 }
[301]1198 uc.SendMessageLabeled(dc.id, &irc.Message{
[297]1199 Command: "NICK",
1200 Params: []string{nick},
1201 })
[42]1202 })
[296]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
[478]1211 dc.nickCM = casemapASCII(dc.nick)
[296]1212 }
[146]1213 case "JOIN":
1214 var namesStr string
1215 if err := parseMessageParams(msg, &namesStr); err != nil {
[48]1216 return err
1217 }
1218
[146]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, ",") {
[145]1225 uc, upstreamName, err := dc.unmarshalEntity(name)
1226 if err != nil {
[158]1227 return err
[145]1228 }
[48]1229
[146]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 }
[301]1239 uc.SendMessageLabeled(dc.id, &irc.Message{
[146]1240 Command: "JOIN",
1241 Params: params,
[145]1242 })
[89]1243
[478]1244 ch := uc.network.channels.Value(upstreamName)
1245 if ch != nil {
[285]1246 // Don't clear the channel key if there's one set
1247 // TODO: add a way to unset the channel key
[435]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 }
[478]1257 uc.network.channels.SetValue(upstreamName, ch)
[285]1258 }
[435]1259 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
[222]1260 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
[89]1261 }
1262 }
[146]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 {
[158]1277 return err
[146]1278 }
1279
[284]1280 if strings.EqualFold(reason, "detach") {
[478]1281 ch := uc.network.channels.Value(upstreamName)
1282 if ch != nil {
[435]1283 uc.network.detach(ch)
1284 } else {
1285 ch = &Channel{
1286 Name: name,
1287 Detached: true,
1288 }
[478]1289 uc.network.channels.SetValue(upstreamName, ch)
[284]1290 }
[435]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 }
[284]1294 } else {
1295 params := []string{upstreamName}
1296 if reason != "" {
1297 params = append(params, reason)
1298 }
[301]1299 uc.SendMessageLabeled(dc.id, &irc.Message{
[284]1300 Command: "PART",
1301 Params: params,
1302 })
[146]1303
[284]1304 if err := uc.network.deleteChannel(upstreamName); err != nil {
1305 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1306 }
[146]1307 }
1308 }
[159]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,
[400]1351 Params: []string{dc.nick, user, channel, "They are on another network"},
[159]1352 }}
1353 }
1354 uc := ucChannel
1355
1356 params := []string{upstreamChannel, upstreamUser}
1357 if reason != "" {
1358 params = append(params, reason)
1359 }
[301]1360 uc.SendMessageLabeled(dc.id, &irc.Message{
[159]1361 Command: "KICK",
1362 Params: params,
1363 })
1364 }
[69]1365 case "MODE":
[46]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
[478]1376 if casemapASCII(name) == dc.nickCM {
[46]1377 if modeStr != "" {
[73]1378 dc.forEachUpstream(func(uc *upstreamConn) {
[301]1379 uc.SendMessageLabeled(dc.id, &irc.Message{
[69]1380 Command: "MODE",
1381 Params: []string{uc.nick, modeStr},
1382 })
[46]1383 })
1384 } else {
[55]1385 dc.SendMessage(&irc.Message{
1386 Prefix: dc.srv.prefix(),
[46]1387 Command: irc.RPL_UMODEIS,
[129]1388 Params: []string{dc.nick, ""}, // TODO
[54]1389 })
[46]1390 }
[139]1391 return nil
[46]1392 }
[139]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:]...)
[301]1409 uc.SendMessageLabeled(dc.id, &irc.Message{
[139]1410 Command: "MODE",
1411 Params: params,
1412 })
1413 } else {
[478]1414 ch := uc.channels.Value(upstreamName)
1415 if ch == nil {
[139]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 })
[162]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 }
[139]1444 }
[160]1445 case "TOPIC":
1446 var channel string
1447 if err := parseMessageParams(msg, &channel); err != nil {
1448 return err
1449 }
1450
[478]1451 uc, upstreamName, err := dc.unmarshalEntity(channel)
[160]1452 if err != nil {
1453 return err
1454 }
1455
1456 if len(msg.Params) > 1 { // setting topic
1457 topic := msg.Params[1]
[301]1458 uc.SendMessageLabeled(dc.id, &irc.Message{
[160]1459 Command: "TOPIC",
[478]1460 Params: []string{upstreamName, topic},
[160]1461 })
1462 } else { // getting topic
[478]1463 ch := uc.channels.Value(upstreamName)
1464 if ch == nil {
[160]1465 return ircError{&irc.Message{
1466 Command: irc.ERR_NOSUCHCHANNEL,
[478]1467 Params: []string{dc.nick, upstreamName, "No such channel"},
[160]1468 }}
1469 }
1470 sendTopic(dc, ch)
1471 }
[177]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 }
[298]1479 var upstream *upstreamConn
[177]1480 var upstreamChannels map[int64][]string
1481 if len(msg.Params) > 0 {
[298]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)
[177]1494 }
1495 }
1496 }
1497
1498 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1499 dc.forEachUpstream(func(uc *upstreamConn) {
[298]1500 if upstream != nil && upstream != uc {
1501 return
1502 }
[177]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 }
[181]1515 uc.trySendLIST(dc.id)
[177]1516 })
[140]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 {
[478]1529 uc, upstreamName, err := dc.unmarshalEntity(channel)
[140]1530 if err != nil {
1531 return err
1532 }
1533
[478]1534 ch := uc.channels.Value(upstreamName)
1535 if ch != nil {
[140]1536 sendNames(dc, ch)
1537 } else {
1538 // NAMES on a channel we have not joined, ask upstream
[176]1539 uc.SendMessageLabeled(dc.id, &irc.Message{
[140]1540 Command: "NAMES",
[478]1541 Params: []string{upstreamName},
[140]1542 })
1543 }
1544 }
[127]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,
[140]1551 Params: []string{dc.nick, "*", "End of /WHO list"},
[127]1552 })
1553 return nil
1554 }
1555
1556 // TODO: support WHO masks
1557 entity := msg.Params[0]
[478]1558 entityCM := casemapASCII(entity)
[127]1559
[478]1560 if entityCM == dc.nickCM {
[142]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,
[184]1565 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
[142]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 }
[478]1574 if entityCM == serviceNickCM {
[343]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 }
[142]1587
[127]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
[176]1600 uc.SendMessageLabeled(dc.id, &irc.Message{
[127]1601 Command: "WHO",
1602 Params: params,
1603 })
[128]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
[478]1625 if casemapASCII(mask) == dc.nickCM {
[142]1626 dc.SendMessage(&irc.Message{
1627 Prefix: dc.srv.prefix(),
1628 Command: irc.RPL_WHOISUSER,
[184]1629 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
[142]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
[128]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 != "" {
[299]1652 if target == mask { // WHOIS nick nick
1653 params = []string{upstreamNick, upstreamNick}
1654 } else {
1655 params = []string{target, upstreamNick}
1656 }
[128]1657 } else {
1658 params = []string{upstreamNick}
1659 }
1660
[176]1661 uc.SendMessageLabeled(dc.id, &irc.Message{
[128]1662 Command: "WHOIS",
1663 Params: params,
1664 })
[58]1665 case "PRIVMSG":
1666 var targetsStr, text string
1667 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1668 return err
1669 }
[303]1670 tags := copyClientTags(msg.Tags)
[58]1671
1672 for _, name := range strings.Split(targetsStr, ",") {
[117]1673 if name == serviceNick {
[431]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 }
[117]1684 handleServicePRIVMSG(dc, text)
1685 continue
1686 }
1687
[127]1688 uc, upstreamName, err := dc.unmarshalEntity(name)
[58]1689 if err != nil {
1690 return err
1691 }
1692
[486]1693 if uc.network.casemap(upstreamName) == "nickserv" {
[95]1694 dc.handleNickServPRIVMSG(uc, text)
1695 }
1696
[268]1697 unmarshaledText := text
1698 if uc.isChannel(upstreamName) {
1699 unmarshaledText = dc.unmarshalText(uc, text)
1700 }
[301]1701 uc.SendMessageLabeled(dc.id, &irc.Message{
[303]1702 Tags: tags,
[58]1703 Command: "PRIVMSG",
[268]1704 Params: []string{upstreamName, unmarshaledText},
[60]1705 })
[105]1706
[303]1707 echoTags := tags.Copy()
1708 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
[113]1709 echoMsg := &irc.Message{
[303]1710 Tags: echoTags,
[113]1711 Prefix: &irc.Prefix{
1712 Name: uc.nick,
1713 User: uc.username,
1714 },
[114]1715 Command: "PRIVMSG",
[113]1716 Params: []string{upstreamName, text},
1717 }
[239]1718 uc.produce(upstreamName, echoMsg, dc)
[435]1719
1720 uc.updateChannelAutoDetach(upstreamName)
[58]1721 }
[164]1722 case "NOTICE":
1723 var targetsStr, text string
1724 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1725 return err
1726 }
[303]1727 tags := copyClientTags(msg.Tags)
[164]1728
1729 for _, name := range strings.Split(targetsStr, ",") {
1730 uc, upstreamName, err := dc.unmarshalEntity(name)
1731 if err != nil {
1732 return err
1733 }
1734
[268]1735 unmarshaledText := text
1736 if uc.isChannel(upstreamName) {
1737 unmarshaledText = dc.unmarshalText(uc, text)
1738 }
[301]1739 uc.SendMessageLabeled(dc.id, &irc.Message{
[303]1740 Tags: tags,
[164]1741 Command: "NOTICE",
[268]1742 Params: []string{upstreamName, unmarshaledText},
[164]1743 })
[435]1744
1745 uc.updateChannelAutoDetach(upstreamName)
[164]1746 }
[303]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 }
[427]1759 if _, ok := uc.caps["message-tags"]; !ok {
1760 continue
1761 }
[303]1762
1763 uc.SendMessageLabeled(dc.id, &irc.Message{
1764 Tags: tags,
1765 Command: "TAGMSG",
1766 Params: []string{upstreamName},
1767 })
[435]1768
1769 uc.updateChannelAutoDetach(upstreamName)
[303]1770 }
[163]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,
[401]1790 Params: []string{dc.nick, user, channel, "They are on another network"},
[163]1791 }}
1792 }
1793 uc := ucChannel
1794
[176]1795 uc.SendMessageLabeled(dc.id, &irc.Message{
[163]1796 Command: "INVITE",
1797 Params: []string{upstreamUser, upstreamChannel},
1798 })
[319]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
[441]1812 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
1813 if !ok {
[319]1814 return ircError{&irc.Message{
1815 Command: irc.ERR_UNKNOWNCOMMAND,
[456]1816 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
[319]1817 }}
1818 }
1819
1820 uc, entity, err := dc.unmarshalEntity(target)
1821 if err != nil {
1822 return err
1823 }
[479]1824 entity = uc.network.casemap(entity)
[319]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",
[456]1831 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Unknown criteria"},
[319]1832 }}
1833 }
1834
1835 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
1836 if err != nil {
1837 return ircError{&irc.Message{
1838 Command: "FAIL",
[456]1839 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Invalid criteria"},
[319]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",
[456]1847 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
[319]1848 }}
1849 }
1850
[387]1851 var history []*irc.Message
[319]1852 switch subcommand {
1853 case "BEFORE":
[441]1854 history, err = store.LoadBeforeTime(uc.network, entity, timestamp, limit)
[360]1855 case "AFTER":
[441]1856 history, err = store.LoadAfterTime(uc.network, entity, timestamp, limit)
[319]1857 default:
[360]1858 // TODO: support LATEST, BETWEEN
[319]1859 return ircError{&irc.Message{
1860 Command: "FAIL",
1861 Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
1862 }}
1863 }
[387]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 })
[13]1886 default:
[55]1887 dc.logger.Printf("unhandled message: %v", msg)
[13]1888 return newUnknownCommandError(msg.Command)
1889 }
[42]1890 return nil
[13]1891}
[95]1892
1893func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1894 username, password, ok := parseNickServCredentials(text, uc.nick)
1895 if !ok {
1896 return
1897 }
1898
[307]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
[95]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
[421]1910 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
[95]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
[182]1929 password = params[0]
[95]1930 } else {
1931 username = params[0]
[182]1932 password = params[1]
[95]1933 }
[182]1934 case "SET":
1935 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1936 username = nick
1937 password = params[1]
1938 }
[340]1939 default:
1940 return "", "", false
[95]1941 }
1942 return username, password, true
1943}
Note: See TracBrowser for help on using the repository browser.