source: code/trunk/downstream.go@ 812

Last change on this file since 812 was 804, checked in by koizumi.aoi, 2 years ago

Drunk as I like

Signed-off-by: Aoi K <koizumi.aoi@…>

File size: 78.2 KB
RevLine 
[804]1package suika
[13]2
3import (
[760]4 "bytes"
[652]5 "context"
[91]6 "crypto/tls"
[112]7 "encoding/base64"
[655]8 "errors"
[13]9 "fmt"
10 "io"
11 "net"
[108]12 "strconv"
[39]13 "strings"
[91]14 "time"
[13]15
[112]16 "github.com/emersion/go-sasl"
[85]17 "golang.org/x/crypto/bcrypt"
[13]18 "gopkg.in/irc.v3"
19)
20
21type ircError struct {
22 Message *irc.Message
23}
24
[85]25func (err ircError) Error() string {
26 return err.Message.String()
27}
28
[13]29func newUnknownCommandError(cmd string) ircError {
30 return ircError{&irc.Message{
31 Command: irc.ERR_UNKNOWNCOMMAND,
32 Params: []string{
33 "*",
34 cmd,
35 "Unknown command",
36 },
37 }}
38}
39
40func newNeedMoreParamsError(cmd string) ircError {
41 return ircError{&irc.Message{
42 Command: irc.ERR_NEEDMOREPARAMS,
43 Params: []string{
44 "*",
45 cmd,
46 "Not enough parameters",
47 },
48 }}
49}
50
[319]51func newChatHistoryError(subcommand string, target string) ircError {
52 return ircError{&irc.Message{
53 Command: "FAIL",
54 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, target, "Messages could not be retrieved"},
55 }}
56}
57
[726]58// authError is an authentication error.
59type authError struct {
60 // Internal error cause. This will not be revealed to the user.
61 err error
62 // Error cause which can safely be sent to the user without compromising
63 // security.
64 reason string
65}
[13]66
[726]67func (err *authError) Error() string {
68 return err.err.Error()
69}
70
71func (err *authError) Unwrap() error {
72 return err.err
73}
74
75// authErrorReason returns the user-friendly reason of an authentication
76// failure.
77func authErrorReason(err error) string {
78 if authErr, ok := err.(*authError); ok {
79 return authErr.reason
80 } else {
81 return "Authentication failed"
82 }
83}
84
85func newInvalidUsernameOrPasswordError(err error) error {
86 return &authError{
87 err: err,
88 reason: "Invalid username or password",
89 }
90}
91
[535]92func parseBouncerNetID(subcommand, s string) (int64, error) {
[532]93 id, err := strconv.ParseInt(s, 10, 64)
94 if err != nil {
95 return 0, ircError{&irc.Message{
96 Command: "FAIL",
[535]97 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, s, "Invalid network ID"},
[532]98 }}
99 }
100 return id, nil
101}
102
[654]103func fillNetworkAddrAttrs(attrs irc.Tags, network *Network) {
104 u, err := network.URL()
105 if err != nil {
106 return
107 }
108
109 hasHostPort := true
110 switch u.Scheme {
111 case "ircs":
112 attrs["tls"] = irc.TagValue("1")
113 case "irc+insecure":
114 attrs["tls"] = irc.TagValue("0")
115 default: // e.g. unix://
116 hasHostPort = false
117 }
118 if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
119 attrs["host"] = irc.TagValue(host)
120 attrs["port"] = irc.TagValue(port)
121 } else if hasHostPort {
122 attrs["host"] = irc.TagValue(u.Host)
123 }
124}
125
[535]126func getNetworkAttrs(network *network) irc.Tags {
127 state := "disconnected"
128 if uc := network.conn; uc != nil {
129 state = "connected"
130 }
131
132 attrs := irc.Tags{
133 "name": irc.TagValue(network.GetName()),
134 "state": irc.TagValue(state),
[664]135 "nickname": irc.TagValue(GetNick(&network.user.User, &network.Network)),
[535]136 }
137
138 if network.Username != "" {
139 attrs["username"] = irc.TagValue(network.Username)
140 }
[568]141 if realname := GetRealname(&network.user.User, &network.Network); realname != "" {
142 attrs["realname"] = irc.TagValue(realname)
[535]143 }
144
[654]145 fillNetworkAddrAttrs(attrs, &network.Network)
146
147 return attrs
148}
149
150func networkAddrFromAttrs(attrs irc.Tags) string {
151 host, ok := attrs.GetTag("host")
152 if !ok {
153 return ""
154 }
155
156 addr := host
157 if port, ok := attrs.GetTag("port"); ok {
158 addr += ":" + port
159 }
160
161 if tlsStr, ok := attrs.GetTag("tls"); ok && tlsStr == "0" {
162 addr = "irc+insecure://" + tlsStr
163 }
164
165 return addr
166}
167
168func updateNetworkAttrs(record *Network, attrs irc.Tags, subcommand string) error {
169 addrAttrs := irc.Tags{}
170 fillNetworkAddrAttrs(addrAttrs, record)
171
172 updateAddr := false
173 for k, v := range attrs {
174 s := string(v)
175 switch k {
176 case "host", "port", "tls":
177 updateAddr = true
178 addrAttrs[k] = v
179 case "name":
180 record.Name = s
181 case "nickname":
182 record.Nick = s
183 case "username":
184 record.Username = s
185 case "realname":
186 record.Realname = s
187 case "pass":
188 record.Pass = s
[535]189 default:
[654]190 return ircError{&irc.Message{
191 Command: "FAIL",
192 Params: []string{"BOUNCER", "UNKNOWN_ATTRIBUTE", subcommand, k, "Unknown attribute"},
193 }}
[535]194 }
[654]195 }
196
197 if updateAddr {
198 record.Addr = networkAddrFromAttrs(addrAttrs)
199 if record.Addr == "" {
200 return ircError{&irc.Message{
201 Command: "FAIL",
202 Params: []string{"BOUNCER", "NEED_ATTRIBUTE", subcommand, "host", "Missing required host attribute"},
203 }}
[535]204 }
205 }
206
[654]207 return nil
[535]208}
209
[793]210// illegalNickChars is the list of characters forbidden in a nickname.
211//
212// ' ' and ':' break the IRC message wire format
213// '@' and '!' break prefixes
214// '*' breaks masks and is the reserved nickname for registration
215// '?' breaks masks
216// '$' breaks server masks in PRIVMSG/NOTICE
217// ',' breaks lists
[794]218// '.' is reserved for server names
219const illegalNickChars = " :@!*?$,."
[404]220
[275]221// permanentDownstreamCaps is the list of always-supported downstream
222// capabilities.
223var permanentDownstreamCaps = map[string]string{
[535]224 "batch": "",
225 "cap-notify": "",
226 "echo-message": "",
227 "invite-notify": "",
228 "message-tags": "",
229 "server-time": "",
[540]230 "setname": "",
[535]231
232 "soju.im/bouncer-networks": "",
233 "soju.im/bouncer-networks-notify": "",
[781]234 "soju.im/read": "",
[275]235}
236
[292]237// needAllDownstreamCaps is the list of downstream capabilities that
238// require support from all upstreams to be enabled
239var needAllDownstreamCaps = map[string]string{
[648]240 "account-notify": "",
241 "account-tag": "",
242 "away-notify": "",
243 "extended-join": "",
244 "multi-prefix": "",
[685]245
246 "draft/extended-monitor": "",
[292]247}
248
[463]249// passthroughIsupport is the set of ISUPPORT tokens that are directly passed
250// through from the upstream server to downstream clients.
251//
252// This is only effective in single-upstream mode.
253var passthroughIsupport = map[string]bool{
[580]254 "AWAYLEN": true,
255 "BOT": true,
256 "CHANLIMIT": true,
257 "CHANMODES": true,
258 "CHANNELLEN": true,
259 "CHANTYPES": true,
260 "CLIENTTAGDENY": true,
[683]261 "ELIST": true,
[580]262 "EXCEPTS": true,
263 "EXTBAN": true,
264 "HOSTLEN": true,
265 "INVEX": true,
266 "KICKLEN": true,
267 "MAXLIST": true,
268 "MAXTARGETS": true,
269 "MODES": true,
[684]270 "MONITOR": true,
[580]271 "NAMELEN": true,
272 "NETWORK": true,
273 "NICKLEN": true,
274 "PREFIX": true,
275 "SAFELIST": true,
276 "TARGMAX": true,
277 "TOPICLEN": true,
278 "USERLEN": true,
279 "UTF8ONLY": true,
[660]280 "WHOX": true,
[463]281}
282
[724]283type downstreamSASL struct {
284 server sasl.Server
285 plainUsername, plainPassword string
[760]286 pendingResp bytes.Buffer
[724]287}
288
[13]289type downstreamConn struct {
[210]290 conn
[22]291
[210]292 id uint64
293
[693]294 registered bool
295 user *user
296 nick string
297 nickCM string
298 rawUsername string
299 networkName string
300 clientName string
301 realname string
302 hostname string
[722]303 account string // RPL_LOGGEDIN/OUT state
[693]304 password string // empty after authentication
305 network *network // can be nil
306 isMultiUpstream bool
[105]307
[590]308 negotiatingCaps bool
[108]309 capVersion int
[275]310 supportedCaps map[string]string
[236]311 caps map[string]bool
[724]312 sasl *downstreamSASL
[108]313
[551]314 lastBatchRef uint64
315
[684]316 monitored casemapMap
[13]317}
318
[347]319func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
320 remoteAddr := ic.RemoteAddr().String()
[323]321 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
[398]322 options := connOptions{Logger: logger}
[55]323 dc := &downstreamConn{
[398]324 conn: *newConn(srv, ic, &options),
[276]325 id: id,
[716]326 nick: "*",
327 nickCM: "*",
[275]328 supportedCaps: make(map[string]string),
[276]329 caps: make(map[string]bool),
[684]330 monitored: newCasemapMap(0),
[22]331 }
[323]332 dc.hostname = remoteAddr
[141]333 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
334 dc.hostname = host
335 }
[275]336 for k, v := range permanentDownstreamCaps {
337 dc.supportedCaps[k] = v
338 }
[725]339 dc.supportedCaps["sasl"] = "PLAIN"
[691]340 // TODO: this is racy, we should only enable chathistory after
341 // authentication and then check that user.msgStore implements
342 // chatHistoryMessageStore
343 if srv.Config().LogPath != "" {
[319]344 dc.supportedCaps["draft/chathistory"] = ""
345 }
[55]346 return dc
[22]347}
348
[55]349func (dc *downstreamConn) prefix() *irc.Prefix {
[27]350 return &irc.Prefix{
[55]351 Name: dc.nick,
[184]352 User: dc.user.Username,
[141]353 Host: dc.hostname,
[27]354 }
355}
356
[90]357func (dc *downstreamConn) forEachNetwork(f func(*network)) {
358 if dc.network != nil {
359 f(dc.network)
[693]360 } else if dc.isMultiUpstream {
[768]361 for _, network := range dc.user.networks {
362 f(network)
363 }
[90]364 }
365}
366
[73]367func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
[693]368 if dc.network == nil && !dc.isMultiUpstream {
[532]369 return
370 }
[73]371 dc.user.forEachUpstream(func(uc *upstreamConn) {
[77]372 if dc.network != nil && uc.network != dc.network {
[73]373 return
374 }
375 f(uc)
376 })
377}
378
[89]379// upstream returns the upstream connection, if any. If there are zero or if
380// there are multiple upstream connections, it returns nil.
381func (dc *downstreamConn) upstream() *upstreamConn {
382 if dc.network == nil {
383 return nil
384 }
[279]385 return dc.network.conn
[89]386}
387
[260]388func isOurNick(net *network, nick string) bool {
389 // TODO: this doesn't account for nick changes
390 if net.conn != nil {
[478]391 return net.casemap(nick) == net.conn.nickCM
[260]392 }
393 // We're not currently connected to the upstream connection, so we don't
394 // know whether this name is our nickname. Best-effort: use the network's
395 // configured nickname and hope it was the one being used when we were
396 // connected.
[664]397 return net.casemap(nick) == net.casemap(GetNick(&net.user.User, &net.Network))
[260]398}
399
[249]400// marshalEntity converts an upstream entity name (ie. channel or nick) into a
401// downstream entity name.
402//
403// This involves adding a "/<network>" suffix if the entity isn't the current
404// user.
[260]405func (dc *downstreamConn) marshalEntity(net *network, name string) string {
[289]406 if isOurNick(net, name) {
407 return dc.nick
408 }
[478]409 name = partialCasemap(net.casemap, name)
[257]410 if dc.network != nil {
[260]411 if dc.network != net {
[804]412 panic("suika: tried to marshal an entity for another network")
[258]413 }
[257]414 return name
[119]415 }
[260]416 return name + "/" + net.GetName()
[119]417}
418
[260]419func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
420 if isOurNick(net, prefix.Name) {
[257]421 return dc.prefix()
422 }
[478]423 prefix.Name = partialCasemap(net.casemap, prefix.Name)
[130]424 if dc.network != nil {
[260]425 if dc.network != net {
[804]426 panic("suika: tried to marshal a user prefix for another network")
[258]427 }
[257]428 return prefix
[119]429 }
[257]430 return &irc.Prefix{
[260]431 Name: prefix.Name + "/" + net.GetName(),
[257]432 User: prefix.User,
433 Host: prefix.Host,
434 }
[119]435}
436
[584]437// unmarshalEntityNetwork converts a downstream entity name (ie. channel or
438// nick) into an upstream entity name.
[249]439//
440// This involves removing the "/<network>" suffix.
[584]441func (dc *downstreamConn) unmarshalEntityNetwork(name string) (*network, string, error) {
[464]442 if dc.network != nil {
[584]443 return dc.network, name, nil
[464]444 }
[727]445 if !dc.isMultiUpstream {
446 return nil, "", ircError{&irc.Message{
447 Command: irc.ERR_NOSUCHCHANNEL,
448 Params: []string{dc.nick, name, "Cannot interact with channels and users on the bouncer connection. Did you mean to use a specific network?"},
449 }}
450 }
[89]451
[584]452 var net *network
[119]453 if i := strings.LastIndexByte(name, '/'); i >= 0 {
[127]454 network := name[i+1:]
[119]455 name = name[:i]
456
[584]457 for _, n := range dc.user.networks {
458 if network == n.GetName() {
459 net = n
460 break
[119]461 }
[584]462 }
[119]463 }
464
[584]465 if net == nil {
[73]466 return nil, "", ircError{&irc.Message{
467 Command: irc.ERR_NOSUCHCHANNEL,
[727]468 Params: []string{dc.nick, name, "Missing network suffix in name"},
[73]469 }}
[69]470 }
[584]471
472 return net, name, nil
[69]473}
474
[584]475// unmarshalEntity is the same as unmarshalEntityNetwork, but returns the
476// upstream connection and fails if the upstream is disconnected.
477func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
478 net, name, err := dc.unmarshalEntityNetwork(name)
479 if err != nil {
480 return nil, "", err
481 }
482
483 if net.conn == nil {
484 return nil, "", ircError{&irc.Message{
485 Command: irc.ERR_NOSUCHCHANNEL,
[727]486 Params: []string{dc.nick, name, "Disconnected from upstream network"},
[584]487 }}
488 }
489
490 return net.conn, name, nil
491}
492
[268]493func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
494 if dc.upstream() != nil {
495 return text
496 }
497 // TODO: smarter parsing that ignores URLs
498 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
499}
500
[711]501func (dc *downstreamConn) ReadMessage() (*irc.Message, error) {
502 msg, err := dc.conn.ReadMessage()
503 if err != nil {
504 return nil, err
505 }
506 dc.srv.metrics.downstreamInMessagesTotal.Inc()
507 return msg, nil
508}
509
[165]510func (dc *downstreamConn) readMessages(ch chan<- event) error {
[22]511 for {
[210]512 msg, err := dc.ReadMessage()
[655]513 if errors.Is(err, io.EOF) {
[22]514 break
515 } else if err != nil {
516 return fmt.Errorf("failed to read IRC command: %v", err)
517 }
518
[165]519 ch <- eventDownstreamMessage{msg, dc}
[22]520 }
521
[45]522 return nil
[22]523}
524
[230]525// SendMessage sends an outgoing message.
526//
527// This can only called from the user goroutine.
[55]528func (dc *downstreamConn) SendMessage(msg *irc.Message) {
[230]529 if !dc.caps["message-tags"] {
[303]530 if msg.Command == "TAGMSG" {
531 return
532 }
[216]533 msg = msg.Copy()
534 for name := range msg.Tags {
535 supported := false
536 switch name {
537 case "time":
[230]538 supported = dc.caps["server-time"]
[559]539 case "account":
540 supported = dc.caps["account"]
[216]541 }
542 if !supported {
543 delete(msg.Tags, name)
544 }
545 }
546 }
[551]547 if !dc.caps["batch"] && msg.Tags["batch"] != "" {
548 msg = msg.Copy()
549 delete(msg.Tags, "batch")
550 }
[419]551 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
552 msg.Params = msg.Params[:1]
553 }
[540]554 if msg.Command == "SETNAME" && !dc.caps["setname"] {
555 return
556 }
[649]557 if msg.Command == "AWAY" && !dc.caps["away-notify"] {
558 return
559 }
[648]560 if msg.Command == "ACCOUNT" && !dc.caps["account-notify"] {
561 return
562 }
[781]563 if msg.Command == "READ" && !dc.caps["soju.im/read"] {
564 return
565 }
[216]566
[711]567 dc.srv.metrics.downstreamOutMessagesTotal.Inc()
[757]568 dc.conn.SendMessage(context.TODO(), msg)
[54]569}
570
[551]571func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef irc.TagValue)) {
572 dc.lastBatchRef++
573 ref := fmt.Sprintf("%v", dc.lastBatchRef)
574
575 if dc.caps["batch"] {
576 dc.SendMessage(&irc.Message{
577 Tags: tags,
578 Prefix: dc.srv.prefix(),
579 Command: "BATCH",
580 Params: append([]string{"+" + ref, typ}, params...),
581 })
582 }
583
584 f(irc.TagValue(ref))
585
586 if dc.caps["batch"] {
587 dc.SendMessage(&irc.Message{
588 Prefix: dc.srv.prefix(),
589 Command: "BATCH",
590 Params: []string{"-" + ref},
591 })
592 }
593}
594
[428]595// sendMessageWithID sends an outgoing message with the specified internal ID.
596func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
597 dc.SendMessage(msg)
598
[665]599 if id == "" || !dc.messageSupportsBacklog(msg) {
[428]600 return
601 }
602
603 dc.sendPing(id)
604}
605
606// advanceMessageWithID advances history to the specified message ID without
607// sending a message. This is useful e.g. for self-messages when echo-message
608// isn't enabled.
609func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
[665]610 if id == "" || !dc.messageSupportsBacklog(msg) {
[428]611 return
612 }
613
614 dc.sendPing(id)
615}
616
617// ackMsgID acknowledges that a message has been received.
618func (dc *downstreamConn) ackMsgID(id string) {
[488]619 netID, entity, err := parseMsgID(id, nil)
[428]620 if err != nil {
621 dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
622 return
623 }
624
[440]625 network := dc.user.getNetworkByID(netID)
[428]626 if network == nil {
627 return
628 }
629
[485]630 network.delivered.StoreID(entity, dc.clientName, id)
[428]631}
632
633func (dc *downstreamConn) sendPing(msgID string) {
[804]634 token := "suika-msgid-" + msgID
[428]635 dc.SendMessage(&irc.Message{
636 Command: "PING",
637 Params: []string{token},
638 })
639}
640
641func (dc *downstreamConn) handlePong(token string) {
[804]642 if !strings.HasPrefix(token, "suika-msgid-") {
[428]643 dc.logger.Printf("received unrecognized PONG token %q", token)
644 return
645 }
[804]646 msgID := strings.TrimPrefix(token, "suika-msgid-")
[428]647 dc.ackMsgID(msgID)
648}
649
[245]650// marshalMessage re-formats a message coming from an upstream connection so
651// that it's suitable for being sent on this downstream connection. Only
[665]652// messages that may appear in logs are supported, except MODE messages which
653// may only appear in single-upstream mode.
[261]654func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
[686]655 msg = msg.Copy()
656 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
657
[665]658 if dc.network != nil {
659 return msg
660 }
661
[227]662 switch msg.Command {
[303]663 case "PRIVMSG", "NOTICE", "TAGMSG":
[261]664 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]665 case "NICK":
666 // Nick change for another user
[261]667 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]668 case "JOIN", "PART":
[261]669 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]670 case "KICK":
[261]671 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
672 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
[245]673 case "TOPIC":
[261]674 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[540]675 case "QUIT", "SETNAME":
[262]676 // This space is intentionally left blank
[227]677 default:
678 panic(fmt.Sprintf("unexpected %q message", msg.Command))
679 }
680
[245]681 return msg
[227]682}
683
[704]684func (dc *downstreamConn) handleMessage(ctx context.Context, msg *irc.Message) error {
685 ctx, cancel := dc.conn.NewContext(ctx)
[702]686 defer cancel()
687
[703]688 ctx, cancel = context.WithTimeout(ctx, handleDownstreamMessageTimeout)
689 defer cancel()
690
[13]691 switch msg.Command {
[28]692 case "QUIT":
[55]693 return dc.Close()
[13]694 default:
[55]695 if dc.registered {
[702]696 return dc.handleMessageRegistered(ctx, msg)
[13]697 } else {
[702]698 return dc.handleMessageUnregistered(ctx, msg)
[13]699 }
700 }
701}
702
[702]703func (dc *downstreamConn) handleMessageUnregistered(ctx context.Context, msg *irc.Message) error {
[13]704 switch msg.Command {
705 case "NICK":
[117]706 var nick string
707 if err := parseMessageParams(msg, &nick); err != nil {
[43]708 return err
[13]709 }
[717]710 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
[404]711 return ircError{&irc.Message{
712 Command: irc.ERR_ERRONEUSNICKNAME,
713 Params: []string{dc.nick, nick, "contains illegal characters"},
714 }}
715 }
[478]716 nickCM := casemapASCII(nick)
717 if nickCM == serviceNickCM {
[117]718 return ircError{&irc.Message{
719 Command: irc.ERR_NICKNAMEINUSE,
720 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
721 }}
722 }
723 dc.nick = nick
[478]724 dc.nickCM = nickCM
[13]725 case "USER":
[117]726 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
[43]727 return err
[13]728 }
[85]729 case "PASS":
730 if err := parseMessageParams(msg, &dc.password); err != nil {
731 return err
732 }
[108]733 case "CAP":
734 var subCmd string
735 if err := parseMessageParams(msg, &subCmd); err != nil {
736 return err
737 }
738 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
739 return err
740 }
[112]741 case "AUTHENTICATE":
[724]742 credentials, err := dc.handleAuthenticateCommand(msg)
743 if err != nil {
744 return err
745 } else if credentials == nil {
746 break
[112]747 }
748
[724]749 if err := dc.authenticate(ctx, credentials.plainUsername, credentials.plainPassword); err != nil {
[726]750 dc.logger.Printf("SASL authentication error for user %q: %v", credentials.plainUsername, err)
[724]751 dc.endSASL(&irc.Message{
[716]752 Prefix: dc.srv.prefix(),
[125]753 Command: irc.ERR_SASLFAIL,
[726]754 Params: []string{dc.nick, authErrorReason(err)},
[112]755 })
[724]756 break
757 }
[112]758
[724]759 // Technically we should send RPL_LOGGEDIN here. However we use
760 // RPL_LOGGEDIN to mirror the upstream connection status. Let's
761 // see how many clients that breaks. See:
762 // https://github.com/ircv3/ircv3-specifications/pull/476
763 dc.endSASL(nil)
[532]764 case "BOUNCER":
765 var subcommand string
766 if err := parseMessageParams(msg, &subcommand); err != nil {
767 return err
768 }
769
770 switch strings.ToUpper(subcommand) {
771 case "BIND":
772 var idStr string
773 if err := parseMessageParams(msg, nil, &idStr); err != nil {
774 return err
775 }
776
777 if dc.user == nil {
778 return ircError{&irc.Message{
779 Command: "FAIL",
780 Params: []string{"BOUNCER", "ACCOUNT_REQUIRED", "BIND", "Authentication needed to bind to bouncer network"},
781 }}
782 }
783
[535]784 id, err := parseBouncerNetID(subcommand, idStr)
[532]785 if err != nil {
786 return err
787 }
788
789 var match *network
[768]790 for _, net := range dc.user.networks {
[532]791 if net.ID == id {
792 match = net
[768]793 break
[532]794 }
[768]795 }
[532]796 if match == nil {
797 return ircError{&irc.Message{
798 Command: "FAIL",
799 Params: []string{"BOUNCER", "INVALID_NETID", idStr, "Unknown network ID"},
800 }}
801 }
802
803 dc.networkName = match.GetName()
804 }
[13]805 default:
[55]806 dc.logger.Printf("unhandled message: %v", msg)
[13]807 return newUnknownCommandError(msg.Command)
808 }
[716]809 if dc.rawUsername != "" && dc.nick != "*" && !dc.negotiatingCaps {
[700]810 return dc.register(ctx)
[13]811 }
812 return nil
813}
814
[108]815func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
[111]816 cmd = strings.ToUpper(cmd)
817
[108]818 switch cmd {
819 case "LS":
820 if len(args) > 0 {
821 var err error
822 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
823 return err
824 }
825 }
[437]826 if !dc.registered && dc.capVersion >= 302 {
827 // Let downstream show everything it supports, and trim
828 // down the available capabilities when upstreams are
829 // known.
830 for k, v := range needAllDownstreamCaps {
831 dc.supportedCaps[k] = v
832 }
833 }
[108]834
[275]835 caps := make([]string, 0, len(dc.supportedCaps))
836 for k, v := range dc.supportedCaps {
837 if dc.capVersion >= 302 && v != "" {
[276]838 caps = append(caps, k+"="+v)
[275]839 } else {
840 caps = append(caps, k)
841 }
[112]842 }
[108]843
844 // TODO: multi-line replies
845 dc.SendMessage(&irc.Message{
846 Prefix: dc.srv.prefix(),
847 Command: "CAP",
[716]848 Params: []string{dc.nick, "LS", strings.Join(caps, " ")},
[108]849 })
850
[275]851 if dc.capVersion >= 302 {
852 // CAP version 302 implicitly enables cap-notify
853 dc.caps["cap-notify"] = true
854 }
855
[108]856 if !dc.registered {
[590]857 dc.negotiatingCaps = true
[108]858 }
859 case "LIST":
860 var caps []string
[521]861 for name, enabled := range dc.caps {
862 if enabled {
863 caps = append(caps, name)
864 }
[108]865 }
866
867 // TODO: multi-line replies
868 dc.SendMessage(&irc.Message{
869 Prefix: dc.srv.prefix(),
870 Command: "CAP",
[716]871 Params: []string{dc.nick, "LIST", strings.Join(caps, " ")},
[108]872 })
873 case "REQ":
874 if len(args) == 0 {
875 return ircError{&irc.Message{
876 Command: err_invalidcapcmd,
[716]877 Params: []string{dc.nick, cmd, "Missing argument in CAP REQ command"},
[108]878 }}
879 }
880
[275]881 // TODO: atomically ack/nak the whole capability set
[108]882 caps := strings.Fields(args[0])
883 ack := true
884 for _, name := range caps {
885 name = strings.ToLower(name)
886 enable := !strings.HasPrefix(name, "-")
887 if !enable {
888 name = strings.TrimPrefix(name, "-")
889 }
890
[275]891 if enable == dc.caps[name] {
[108]892 continue
893 }
894
[275]895 _, ok := dc.supportedCaps[name]
896 if !ok {
[108]897 ack = false
[275]898 break
[108]899 }
[275]900
901 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
902 // cap-notify cannot be disabled with CAP version 302
903 ack = false
904 break
905 }
906
907 dc.caps[name] = enable
[108]908 }
909
910 reply := "NAK"
911 if ack {
912 reply = "ACK"
913 }
914 dc.SendMessage(&irc.Message{
915 Prefix: dc.srv.prefix(),
916 Command: "CAP",
[716]917 Params: []string{dc.nick, reply, args[0]},
[108]918 })
[590]919
920 if !dc.registered {
921 dc.negotiatingCaps = true
922 }
[108]923 case "END":
[590]924 dc.negotiatingCaps = false
[108]925 default:
926 return ircError{&irc.Message{
927 Command: err_invalidcapcmd,
[716]928 Params: []string{dc.nick, cmd, "Unknown CAP command"},
[108]929 }}
930 }
931 return nil
932}
933
[724]934func (dc *downstreamConn) handleAuthenticateCommand(msg *irc.Message) (result *downstreamSASL, err error) {
935 defer func() {
936 if err != nil {
937 dc.sasl = nil
938 }
939 }()
940
941 if !dc.caps["sasl"] {
942 return nil, ircError{&irc.Message{
943 Prefix: dc.srv.prefix(),
944 Command: irc.ERR_SASLFAIL,
[754]945 Params: []string{dc.nick, "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
[724]946 }}
947 }
948 if len(msg.Params) == 0 {
949 return nil, ircError{&irc.Message{
950 Prefix: dc.srv.prefix(),
951 Command: irc.ERR_SASLFAIL,
[754]952 Params: []string{dc.nick, "Missing AUTHENTICATE argument"},
[724]953 }}
954 }
955 if msg.Params[0] == "*" {
956 return nil, ircError{&irc.Message{
957 Prefix: dc.srv.prefix(),
958 Command: irc.ERR_SASLABORTED,
[754]959 Params: []string{dc.nick, "SASL authentication aborted"},
[724]960 }}
961 }
962
963 var resp []byte
964 if dc.sasl == nil {
965 mech := strings.ToUpper(msg.Params[0])
966 var server sasl.Server
967 switch mech {
968 case "PLAIN":
969 server = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
970 dc.sasl.plainUsername = username
971 dc.sasl.plainPassword = password
972 return nil
973 }))
974 default:
975 return nil, ircError{&irc.Message{
976 Prefix: dc.srv.prefix(),
977 Command: irc.ERR_SASLFAIL,
[754]978 Params: []string{dc.nick, fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
[724]979 }}
980 }
981
982 dc.sasl = &downstreamSASL{server: server}
983 } else {
[760]984 chunk := msg.Params[0]
985 if chunk == "+" {
986 chunk = ""
987 }
988
989 if dc.sasl.pendingResp.Len()+len(chunk) > 10*1024 {
[724]990 return nil, ircError{&irc.Message{
991 Prefix: dc.srv.prefix(),
992 Command: irc.ERR_SASLFAIL,
[760]993 Params: []string{dc.nick, "Response too long"},
994 }}
995 }
996
997 dc.sasl.pendingResp.WriteString(chunk)
998
[761]999 if len(chunk) == maxSASLLength {
[760]1000 return nil, nil // Multi-line response, wait for the next command
1001 }
1002
1003 resp, err = base64.StdEncoding.DecodeString(dc.sasl.pendingResp.String())
1004 if err != nil {
1005 return nil, ircError{&irc.Message{
1006 Prefix: dc.srv.prefix(),
1007 Command: irc.ERR_SASLFAIL,
[754]1008 Params: []string{dc.nick, "Invalid base64-encoded response"},
[724]1009 }}
1010 }
[760]1011
1012 dc.sasl.pendingResp.Reset()
[724]1013 }
1014
1015 challenge, done, err := dc.sasl.server.Next(resp)
1016 if err != nil {
1017 return nil, err
1018 } else if done {
1019 return dc.sasl, nil
1020 } else {
1021 challengeStr := "+"
1022 if len(challenge) > 0 {
1023 challengeStr = base64.StdEncoding.EncodeToString(challenge)
1024 }
1025
1026 // TODO: multi-line messages
1027 dc.SendMessage(&irc.Message{
1028 Prefix: dc.srv.prefix(),
1029 Command: "AUTHENTICATE",
1030 Params: []string{challengeStr},
1031 })
1032 return nil, nil
1033 }
1034}
1035
1036func (dc *downstreamConn) endSASL(msg *irc.Message) {
1037 if dc.sasl == nil {
1038 return
1039 }
1040
1041 dc.sasl = nil
1042
1043 if msg != nil {
1044 dc.SendMessage(msg)
1045 } else {
1046 dc.SendMessage(&irc.Message{
1047 Prefix: dc.srv.prefix(),
1048 Command: irc.RPL_SASLSUCCESS,
1049 Params: []string{dc.nick, "SASL authentication successful"},
1050 })
1051 }
1052}
1053
[275]1054func (dc *downstreamConn) setSupportedCap(name, value string) {
1055 prevValue, hasPrev := dc.supportedCaps[name]
1056 changed := !hasPrev || prevValue != value
1057 dc.supportedCaps[name] = value
1058
1059 if !dc.caps["cap-notify"] || !changed {
1060 return
1061 }
1062
1063 cap := name
1064 if value != "" && dc.capVersion >= 302 {
1065 cap = name + "=" + value
1066 }
1067
1068 dc.SendMessage(&irc.Message{
1069 Prefix: dc.srv.prefix(),
1070 Command: "CAP",
[716]1071 Params: []string{dc.nick, "NEW", cap},
[275]1072 })
1073}
1074
1075func (dc *downstreamConn) unsetSupportedCap(name string) {
1076 _, hasPrev := dc.supportedCaps[name]
1077 delete(dc.supportedCaps, name)
1078 delete(dc.caps, name)
1079
1080 if !dc.caps["cap-notify"] || !hasPrev {
1081 return
1082 }
1083
1084 dc.SendMessage(&irc.Message{
1085 Prefix: dc.srv.prefix(),
1086 Command: "CAP",
[716]1087 Params: []string{dc.nick, "DEL", name},
[275]1088 })
1089}
1090
[276]1091func (dc *downstreamConn) updateSupportedCaps() {
[292]1092 supportedCaps := make(map[string]bool)
1093 for cap := range needAllDownstreamCaps {
1094 supportedCaps[cap] = true
1095 }
[276]1096 dc.forEachUpstream(func(uc *upstreamConn) {
[292]1097 for cap, supported := range supportedCaps {
1098 supportedCaps[cap] = supported && uc.caps[cap]
1099 }
[276]1100 })
1101
[292]1102 for cap, supported := range supportedCaps {
1103 if supported {
1104 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
1105 } else {
1106 dc.unsetSupportedCap(cap)
1107 }
[276]1108 }
[665]1109
[725]1110 if uc := dc.upstream(); uc != nil && uc.supportsSASL("PLAIN") {
1111 dc.setSupportedCap("sasl", "PLAIN")
1112 } else if dc.network != nil {
1113 dc.unsetSupportedCap("sasl")
1114 }
1115
[729]1116 if uc := dc.upstream(); uc != nil && uc.caps["draft/account-registration"] {
1117 // Strip "before-connect", because we require downstreams to be fully
1118 // connected before attempting account registration.
1119 values := strings.Split(uc.supportedCaps["draft/account-registration"], ",")
1120 for i, v := range values {
1121 if v == "before-connect" {
1122 values = append(values[:i], values[i+1:]...)
1123 break
1124 }
1125 }
1126 dc.setSupportedCap("draft/account-registration", strings.Join(values, ","))
1127 } else {
1128 dc.unsetSupportedCap("draft/account-registration")
1129 }
1130
[691]1131 if _, ok := dc.user.msgStore.(chatHistoryMessageStore); ok && dc.network != nil {
[665]1132 dc.setSupportedCap("draft/event-playback", "")
1133 } else {
1134 dc.unsetSupportedCap("draft/event-playback")
1135 }
[276]1136}
1137
[296]1138func (dc *downstreamConn) updateNick() {
1139 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
1140 dc.SendMessage(&irc.Message{
1141 Prefix: dc.prefix(),
1142 Command: "NICK",
1143 Params: []string{uc.nick},
1144 })
1145 dc.nick = uc.nick
[478]1146 dc.nickCM = casemapASCII(dc.nick)
[296]1147 }
1148}
1149
[540]1150func (dc *downstreamConn) updateRealname() {
1151 if uc := dc.upstream(); uc != nil && uc.realname != dc.realname && dc.caps["setname"] {
1152 dc.SendMessage(&irc.Message{
1153 Prefix: dc.prefix(),
1154 Command: "SETNAME",
1155 Params: []string{uc.realname},
1156 })
1157 dc.realname = uc.realname
1158 }
1159}
1160
[722]1161func (dc *downstreamConn) updateAccount() {
[723]1162 var account string
1163 if dc.network == nil {
1164 account = dc.user.Username
1165 } else if uc := dc.upstream(); uc != nil {
1166 account = uc.account
1167 } else {
[722]1168 return
1169 }
1170
[723]1171 if dc.account == account || !dc.caps["sasl"] {
1172 return
1173 }
1174
1175 if account != "" {
[722]1176 dc.SendMessage(&irc.Message{
1177 Prefix: dc.srv.prefix(),
1178 Command: irc.RPL_LOGGEDIN,
[723]1179 Params: []string{dc.nick, dc.prefix().String(), account, "You are logged in as " + account},
[722]1180 })
1181 } else {
1182 dc.SendMessage(&irc.Message{
1183 Prefix: dc.srv.prefix(),
1184 Command: irc.RPL_LOGGEDOUT,
1185 Params: []string{dc.nick, dc.prefix().String(), "You are logged out"},
1186 })
1187 }
1188
[723]1189 dc.account = account
[722]1190}
1191
[698]1192func sanityCheckServer(ctx context.Context, addr string) error {
[699]1193 ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
[698]1194 defer cancel()
1195
1196 conn, err := new(tls.Dialer).DialContext(ctx, "tcp", addr)
[91]1197 if err != nil {
1198 return err
1199 }
[698]1200
[91]1201 return conn.Close()
1202}
1203
[183]1204func unmarshalUsername(rawUsername string) (username, client, network string) {
[112]1205 username = rawUsername
[183]1206
1207 i := strings.IndexAny(username, "/@")
1208 j := strings.LastIndexAny(username, "/@")
1209 if i >= 0 {
1210 username = rawUsername[:i]
[73]1211 }
[183]1212 if j >= 0 {
[190]1213 if rawUsername[j] == '@' {
1214 client = rawUsername[j+1:]
1215 } else {
1216 network = rawUsername[j+1:]
1217 }
[73]1218 }
[183]1219 if i >= 0 && j >= 0 && i < j {
[190]1220 if rawUsername[i] == '@' {
1221 client = rawUsername[i+1 : j]
1222 } else {
1223 network = rawUsername[i+1 : j]
1224 }
[183]1225 }
1226
1227 return username, client, network
[112]1228}
[73]1229
[700]1230func (dc *downstreamConn) authenticate(ctx context.Context, username, password string) error {
[183]1231 username, clientName, networkName := unmarshalUsername(username)
[168]1232
[700]1233 u, err := dc.srv.db.GetUser(ctx, username)
[173]1234 if err != nil {
[726]1235 return newInvalidUsernameOrPasswordError(fmt.Errorf("user not found: %w", err))
[168]1236 }
1237
[322]1238 // Password auth disabled
1239 if u.Password == "" {
[726]1240 return newInvalidUsernameOrPasswordError(fmt.Errorf("password auth disabled"))
[322]1241 }
1242
[173]1243 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
[168]1244 if err != nil {
[726]1245 return newInvalidUsernameOrPasswordError(fmt.Errorf("wrong password"))
[168]1246 }
1247
[173]1248 dc.user = dc.srv.getUser(username)
1249 if dc.user == nil {
[726]1250 return fmt.Errorf("user not active")
[173]1251 }
[183]1252 dc.clientName = clientName
[168]1253 dc.networkName = networkName
1254 return nil
1255}
1256
[700]1257func (dc *downstreamConn) register(ctx context.Context) error {
[168]1258 if dc.registered {
[788]1259 panic("tried to register twice")
[168]1260 }
1261
[724]1262 if dc.sasl != nil {
1263 dc.endSASL(&irc.Message{
[721]1264 Prefix: dc.srv.prefix(),
1265 Command: irc.ERR_SASLABORTED,
[754]1266 Params: []string{dc.nick, "SASL authentication aborted"},
[721]1267 })
1268 }
1269
[168]1270 password := dc.password
1271 dc.password = ""
1272 if dc.user == nil {
[753]1273 if password == "" {
1274 if dc.caps["sasl"] {
1275 return ircError{&irc.Message{
1276 Command: "FAIL",
1277 Params: []string{"*", "ACCOUNT_REQUIRED", "Authentication required"},
1278 }}
1279 } else {
1280 return ircError{&irc.Message{
1281 Command: irc.ERR_PASSWDMISMATCH,
1282 Params: []string{dc.nick, "Authentication required"},
1283 }}
1284 }
1285 }
1286
[700]1287 if err := dc.authenticate(ctx, dc.rawUsername, password); err != nil {
[726]1288 dc.logger.Printf("PASS authentication error for user %q: %v", dc.rawUsername, err)
1289 return ircError{&irc.Message{
1290 Command: irc.ERR_PASSWDMISMATCH,
[753]1291 Params: []string{dc.nick, authErrorReason(err)},
[726]1292 }}
[168]1293 }
1294 }
1295
[789]1296 _, fallbackClientName, fallbackNetworkName := unmarshalUsername(dc.rawUsername)
1297 if dc.clientName == "" {
1298 dc.clientName = fallbackClientName
[792]1299 } else if fallbackClientName != "" && dc.clientName != fallbackClientName {
[790]1300 return ircError{&irc.Message{
1301 Command: irc.ERR_ERRONEUSNICKNAME,
1302 Params: []string{dc.nick, "Client name mismatch in usernames"},
1303 }}
[168]1304 }
[789]1305 if dc.networkName == "" {
1306 dc.networkName = fallbackNetworkName
[792]1307 } else if fallbackNetworkName != "" && dc.networkName != fallbackNetworkName {
[790]1308 return ircError{&irc.Message{
1309 Command: irc.ERR_ERRONEUSNICKNAME,
1310 Params: []string{dc.nick, "Network name mismatch in usernames"},
1311 }}
[789]1312 }
[168]1313
1314 dc.registered = true
[184]1315 dc.logger.Printf("registration complete for user %q", dc.user.Username)
[168]1316 return nil
1317}
1318
[701]1319func (dc *downstreamConn) loadNetwork(ctx context.Context) error {
[168]1320 if dc.networkName == "" {
[112]1321 return nil
1322 }
[85]1323
[168]1324 network := dc.user.getNetwork(dc.networkName)
[112]1325 if network == nil {
[168]1326 addr := dc.networkName
[112]1327 if !strings.ContainsRune(addr, ':') {
1328 addr = addr + ":6697"
1329 }
1330
1331 dc.logger.Printf("trying to connect to new network %q", addr)
[701]1332 if err := sanityCheckServer(ctx, addr); err != nil {
[112]1333 dc.logger.Printf("failed to connect to %q: %v", addr, err)
1334 return ircError{&irc.Message{
1335 Command: irc.ERR_PASSWDMISMATCH,
[754]1336 Params: []string{dc.nick, fmt.Sprintf("Failed to connect to %q", dc.networkName)},
[112]1337 }}
1338 }
1339
[354]1340 // Some clients only allow specifying the nickname (and use the
1341 // nickname as a username too). Strip the network name from the
1342 // nickname when auto-saving networks.
1343 nick, _, _ := unmarshalUsername(dc.nick)
1344
[168]1345 dc.logger.Printf("auto-saving network %q", dc.networkName)
[112]1346 var err error
[701]1347 network, err = dc.user.createNetwork(ctx, &Network{
[542]1348 Addr: dc.networkName,
1349 Nick: nick,
1350 Enabled: true,
[120]1351 })
[112]1352 if err != nil {
1353 return err
1354 }
1355 }
1356
1357 dc.network = network
1358 return nil
1359}
1360
[701]1361func (dc *downstreamConn) welcome(ctx context.Context) error {
[168]1362 if dc.user == nil || !dc.registered {
1363 panic("tried to welcome an unregistered connection")
[37]1364 }
1365
[750]1366 remoteAddr := dc.conn.RemoteAddr().String()
1367 dc.logger = &prefixLogger{dc.srv.Logger, fmt.Sprintf("user %q: downstream %q: ", dc.user.Username, remoteAddr)}
1368
[168]1369 // TODO: doing this might take some time. We should do it in dc.register
1370 // instead, but we'll potentially be adding a new network and this must be
1371 // done in the user goroutine.
[701]1372 if err := dc.loadNetwork(ctx); err != nil {
[168]1373 return err
[85]1374 }
1375
[694]1376 if dc.network == nil && !dc.caps["soju.im/bouncer-networks"] && dc.srv.Config().MultiUpstream {
[693]1377 dc.isMultiUpstream = true
1378 }
1379
[706]1380 dc.updateSupportedCaps()
1381
[446]1382 isupport := []string{
[670]1383 fmt.Sprintf("CHATHISTORY=%v", chatHistoryLimit),
[478]1384 "CASEMAPPING=ascii",
[446]1385 }
1386
[532]1387 if dc.network != nil {
1388 isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
1389 }
[691]1390 if title := dc.srv.Config().Title; dc.network == nil && title != "" {
1391 isupport = append(isupport, "NETWORK="+encodeISUPPORT(title))
[662]1392 }
[693]1393 if dc.network == nil && !dc.isMultiUpstream {
[660]1394 isupport = append(isupport, "WHOX")
1395 }
1396
[463]1397 if uc := dc.upstream(); uc != nil {
1398 for k := range passthroughIsupport {
1399 v, ok := uc.isupport[k]
1400 if !ok {
1401 continue
1402 }
1403 if v != nil {
1404 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
1405 } else {
1406 isupport = append(isupport, k)
1407 }
1408 }
[447]1409 }
1410
[55]1411 dc.SendMessage(&irc.Message{
1412 Prefix: dc.srv.prefix(),
[13]1413 Command: irc.RPL_WELCOME,
[804]1414 Params: []string{dc.nick, "Welcome to suika, " + dc.nick},
[54]1415 })
[55]1416 dc.SendMessage(&irc.Message{
1417 Prefix: dc.srv.prefix(),
[13]1418 Command: irc.RPL_YOURHOST,
[691]1419 Params: []string{dc.nick, "Your host is " + dc.srv.Config().Hostname},
[54]1420 })
[55]1421 dc.SendMessage(&irc.Message{
1422 Prefix: dc.srv.prefix(),
[13]1423 Command: irc.RPL_MYINFO,
[804]1424 Params: []string{dc.nick, dc.srv.Config().Hostname, "suika", "aiwroO", "OovaimnqpsrtklbeI"},
[54]1425 })
[463]1426 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
1427 dc.SendMessage(msg)
1428 }
[553]1429 if uc := dc.upstream(); uc != nil {
1430 dc.SendMessage(&irc.Message{
1431 Prefix: dc.srv.prefix(),
1432 Command: irc.RPL_UMODEIS,
[672]1433 Params: []string{dc.nick, "+" + string(uc.modes)},
[553]1434 })
1435 }
[693]1436 if dc.network == nil && !dc.isMultiUpstream && dc.user.Admin {
[671]1437 dc.SendMessage(&irc.Message{
1438 Prefix: dc.srv.prefix(),
1439 Command: irc.RPL_UMODEIS,
1440 Params: []string{dc.nick, "+o"},
1441 })
1442 }
[13]1443
[706]1444 dc.updateNick()
1445 dc.updateRealname()
[722]1446 dc.updateAccount()
[706]1447
[691]1448 if motd := dc.user.srv.Config().MOTD; motd != "" && dc.network == nil {
[636]1449 for _, msg := range generateMOTD(dc.srv.prefix(), dc.nick, motd) {
1450 dc.SendMessage(msg)
1451 }
1452 } else {
1453 motdHint := "No MOTD"
1454 if dc.network != nil {
1455 motdHint = "Use /motd to read the message of the day"
1456 }
1457 dc.SendMessage(&irc.Message{
1458 Prefix: dc.srv.prefix(),
1459 Command: irc.ERR_NOMOTD,
1460 Params: []string{dc.nick, motdHint},
1461 })
1462 }
1463
[535]1464 if dc.caps["soju.im/bouncer-networks-notify"] {
[551]1465 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
[768]1466 for _, network := range dc.user.networks {
[551]1467 idStr := fmt.Sprintf("%v", network.ID)
1468 attrs := getNetworkAttrs(network)
1469 dc.SendMessage(&irc.Message{
1470 Tags: irc.Tags{"batch": batchRef},
1471 Prefix: dc.srv.prefix(),
1472 Command: "BOUNCER",
1473 Params: []string{"NETWORK", idStr, attrs.String()},
1474 })
[768]1475 }
[535]1476 })
1477 }
1478
[73]1479 dc.forEachUpstream(func(uc *upstreamConn) {
[478]1480 for _, entry := range uc.channels.innerMap {
1481 ch := entry.value.(*upstreamChannel)
[284]1482 if !ch.complete {
1483 continue
1484 }
[478]1485 record := uc.network.channels.Value(ch.Name)
1486 if record != nil && record.Detached {
[284]1487 continue
1488 }
[132]1489
[284]1490 dc.SendMessage(&irc.Message{
1491 Prefix: dc.prefix(),
1492 Command: "JOIN",
1493 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1494 })
1495
[781]1496 forwardChannel(ctx, dc, ch)
[30]1497 }
[143]1498 })
[50]1499
[143]1500 dc.forEachNetwork(func(net *network) {
[496]1501 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1502 return
1503 }
1504
[253]1505 // Only send history if we're the first connected client with that name
1506 // for the network
[482]1507 firstClient := true
1508 dc.user.forEachDownstream(func(c *downstreamConn) {
1509 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1510 firstClient = false
1511 }
1512 })
1513 if firstClient {
[485]1514 net.delivered.ForEachTarget(func(target string) {
[495]1515 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1516 if lastDelivered == "" {
1517 return
1518 }
1519
[701]1520 dc.sendTargetBacklog(ctx, net, target, lastDelivered)
[495]1521
1522 // Fast-forward history to last message
1523 targetCM := net.casemap(target)
[666]1524 lastID, err := dc.user.msgStore.LastMsgID(&net.Network, targetCM, time.Now())
[495]1525 if err != nil {
1526 dc.logger.Printf("failed to get last message ID: %v", err)
1527 return
1528 }
1529 net.delivered.StoreID(target, dc.clientName, lastID)
[485]1530 })
[227]1531 }
[253]1532 })
[57]1533
[253]1534 return nil
1535}
[144]1536
[665]1537// messageSupportsBacklog checks whether the provided message can be sent as
[428]1538// part of an history batch.
[665]1539func (dc *downstreamConn) messageSupportsBacklog(msg *irc.Message) bool {
[428]1540 // Don't replay all messages, because that would mess up client
1541 // state. For instance we just sent the list of users, sending
1542 // PART messages for one of these users would be incorrect.
1543 switch msg.Command {
1544 case "PRIVMSG", "NOTICE":
1545 return true
1546 }
1547 return false
1548}
1549
[701]1550func (dc *downstreamConn) sendTargetBacklog(ctx context.Context, net *network, target, msgID string) {
[423]1551 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
[319]1552 return
1553 }
[485]1554
[499]1555 ch := net.channels.Value(target)
1556
[701]1557 ctx, cancel := context.WithTimeout(ctx, backlogTimeout)
[667]1558 defer cancel()
1559
[484]1560 targetCM := net.casemap(target)
[670]1561 history, err := dc.user.msgStore.LoadLatestID(ctx, &net.Network, targetCM, msgID, backlogLimit)
[452]1562 if err != nil {
[495]1563 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
[452]1564 return
1565 }
[253]1566
[551]1567 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1568 for _, msg := range history {
1569 if ch != nil && ch.Detached {
1570 if net.detachedMessageNeedsRelay(ch, msg) {
1571 dc.relayDetachedMessage(net, msg)
1572 }
1573 } else {
[651]1574 msg.Tags["batch"] = batchRef
[551]1575 dc.SendMessage(dc.marshalMessage(msg, net))
[499]1576 }
[256]1577 }
[551]1578 })
[13]1579}
1580
[499]1581func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1582 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1583 return
1584 }
1585
1586 sender := msg.Prefix.Name
1587 target, text := msg.Params[0], msg.Params[1]
1588 if net.isHighlight(msg) {
1589 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1590 } else {
1591 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1592 }
1593}
1594
[103]1595func (dc *downstreamConn) runUntilRegistered() error {
[704]1596 ctx, cancel := context.WithTimeout(context.TODO(), downstreamRegisterTimeout)
1597 defer cancel()
1598
1599 // Close the connection with an error if the deadline is exceeded
1600 go func() {
1601 <-ctx.Done()
1602 if err := ctx.Err(); err == context.DeadlineExceeded {
1603 dc.SendMessage(&irc.Message{
1604 Prefix: dc.srv.prefix(),
1605 Command: "ERROR",
1606 Params: []string{"Connection registration timed out"},
1607 })
1608 dc.Close()
1609 }
1610 }()
1611
[103]1612 for !dc.registered {
[212]1613 msg, err := dc.ReadMessage()
[106]1614 if err != nil {
[655]1615 return fmt.Errorf("failed to read IRC command: %w", err)
[103]1616 }
1617
[704]1618 err = dc.handleMessage(ctx, msg)
[103]1619 if ircErr, ok := err.(ircError); ok {
1620 ircErr.Message.Prefix = dc.srv.prefix()
1621 dc.SendMessage(ircErr.Message)
1622 } else if err != nil {
1623 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1624 }
1625 }
1626
1627 return nil
1628}
1629
[702]1630func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.Message) error {
[13]1631 switch msg.Command {
[111]1632 case "CAP":
1633 var subCmd string
1634 if err := parseMessageParams(msg, &subCmd); err != nil {
1635 return err
1636 }
1637 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1638 return err
1639 }
[107]1640 case "PING":
[412]1641 var source, destination string
1642 if err := parseMessageParams(msg, &source); err != nil {
1643 return err
1644 }
1645 if len(msg.Params) > 1 {
1646 destination = msg.Params[1]
1647 }
[691]1648 hostname := dc.srv.Config().Hostname
1649 if destination != "" && destination != hostname {
[412]1650 return ircError{&irc.Message{
1651 Command: irc.ERR_NOSUCHSERVER,
[413]1652 Params: []string{dc.nick, destination, "No such server"},
[412]1653 }}
1654 }
[107]1655 dc.SendMessage(&irc.Message{
1656 Prefix: dc.srv.prefix(),
1657 Command: "PONG",
[691]1658 Params: []string{hostname, source},
[107]1659 })
1660 return nil
[428]1661 case "PONG":
1662 if len(msg.Params) == 0 {
1663 return newNeedMoreParamsError(msg.Command)
1664 }
1665 token := msg.Params[len(msg.Params)-1]
1666 dc.handlePong(token)
[42]1667 case "USER":
[13]1668 return ircError{&irc.Message{
1669 Command: irc.ERR_ALREADYREGISTERED,
[55]1670 Params: []string{dc.nick, "You may not reregister"},
[13]1671 }}
[42]1672 case "NICK":
[429]1673 var rawNick string
1674 if err := parseMessageParams(msg, &rawNick); err != nil {
[90]1675 return err
1676 }
1677
[429]1678 nick := rawNick
[297]1679 var upstream *upstreamConn
1680 if dc.upstream() == nil {
1681 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1682 if err == nil { // NICK nick/network: NICK only on a specific upstream
1683 upstream = uc
1684 nick = unmarshaledNick
1685 }
1686 }
1687
[717]1688 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
[404]1689 return ircError{&irc.Message{
1690 Command: irc.ERR_ERRONEUSNICKNAME,
[430]1691 Params: []string{dc.nick, rawNick, "contains illegal characters"},
[404]1692 }}
1693 }
[478]1694 if casemapASCII(nick) == serviceNickCM {
[429]1695 return ircError{&irc.Message{
1696 Command: irc.ERR_NICKNAMEINUSE,
1697 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1698 }}
1699 }
[404]1700
[90]1701 var err error
1702 dc.forEachNetwork(func(n *network) {
[297]1703 if err != nil || (upstream != nil && upstream.network != n) {
[90]1704 return
1705 }
1706 n.Nick = nick
[675]1707 err = dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network)
[90]1708 })
1709 if err != nil {
1710 return err
1711 }
1712
[73]1713 dc.forEachUpstream(func(uc *upstreamConn) {
[297]1714 if upstream != nil && upstream != uc {
1715 return
1716 }
[757]1717 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[297]1718 Command: "NICK",
1719 Params: []string{nick},
1720 })
[42]1721 })
[296]1722
[512]1723 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
[296]1724 dc.SendMessage(&irc.Message{
1725 Prefix: dc.prefix(),
1726 Command: "NICK",
1727 Params: []string{nick},
1728 })
1729 dc.nick = nick
[478]1730 dc.nickCM = casemapASCII(dc.nick)
[296]1731 }
[540]1732 case "SETNAME":
1733 var realname string
1734 if err := parseMessageParams(msg, &realname); err != nil {
1735 return err
1736 }
1737
[568]1738 // If the client just resets to the default, just wipe the per-network
1739 // preference
1740 storeRealname := realname
1741 if realname == dc.user.Realname {
1742 storeRealname = ""
1743 }
1744
[540]1745 var storeErr error
1746 var needUpdate []Network
1747 dc.forEachNetwork(func(n *network) {
1748 // We only need to call updateNetwork for upstreams that don't
1749 // support setname
1750 if uc := n.conn; uc != nil && uc.caps["setname"] {
[757]1751 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[540]1752 Command: "SETNAME",
1753 Params: []string{realname},
1754 })
1755
[568]1756 n.Realname = storeRealname
[675]1757 if err := dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network); err != nil {
[540]1758 dc.logger.Printf("failed to store network realname: %v", err)
1759 storeErr = err
1760 }
1761 return
1762 }
1763
1764 record := n.Network // copy network record because we'll mutate it
[568]1765 record.Realname = storeRealname
[540]1766 needUpdate = append(needUpdate, record)
1767 })
1768
1769 // Walk the network list as a second step, because updateNetwork
1770 // mutates the original list
1771 for _, record := range needUpdate {
[676]1772 if _, err := dc.user.updateNetwork(ctx, &record); err != nil {
[540]1773 dc.logger.Printf("failed to update network realname: %v", err)
1774 storeErr = err
1775 }
1776 }
1777 if storeErr != nil {
1778 return ircError{&irc.Message{
1779 Command: "FAIL",
1780 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1781 }}
1782 }
1783
[651]1784 if dc.upstream() == nil {
[540]1785 dc.SendMessage(&irc.Message{
1786 Prefix: dc.prefix(),
1787 Command: "SETNAME",
1788 Params: []string{realname},
1789 })
1790 }
[146]1791 case "JOIN":
1792 var namesStr string
1793 if err := parseMessageParams(msg, &namesStr); err != nil {
[48]1794 return err
1795 }
1796
[146]1797 var keys []string
1798 if len(msg.Params) > 1 {
1799 keys = strings.Split(msg.Params[1], ",")
1800 }
1801
1802 for i, name := range strings.Split(namesStr, ",") {
[145]1803 uc, upstreamName, err := dc.unmarshalEntity(name)
1804 if err != nil {
[158]1805 return err
[145]1806 }
[48]1807
[146]1808 var key string
1809 if len(keys) > i {
1810 key = keys[i]
1811 }
1812
[545]1813 if !uc.isChannel(upstreamName) {
1814 dc.SendMessage(&irc.Message{
1815 Prefix: dc.srv.prefix(),
1816 Command: irc.ERR_NOSUCHCHANNEL,
1817 Params: []string{name, "Not a channel name"},
1818 })
1819 continue
1820 }
1821
[758]1822 // Most servers ignore duplicate JOIN messages. We ignore them here
1823 // because some clients automatically send JOIN messages in bulk
1824 // when reconnecting to the bouncer. We don't want to flood the
1825 // upstream connection with these.
1826 if !uc.channels.Has(upstreamName) {
1827 params := []string{upstreamName}
1828 if key != "" {
1829 params = append(params, key)
1830 }
1831 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1832 Command: "JOIN",
1833 Params: params,
1834 })
[146]1835 }
[89]1836
[478]1837 ch := uc.network.channels.Value(upstreamName)
1838 if ch != nil {
[285]1839 // Don't clear the channel key if there's one set
1840 // TODO: add a way to unset the channel key
[435]1841 if key != "" {
1842 ch.Key = key
1843 }
[781]1844 uc.network.attach(ctx, ch)
[435]1845 } else {
1846 ch = &Channel{
1847 Name: upstreamName,
1848 Key: key,
1849 }
[478]1850 uc.network.channels.SetValue(upstreamName, ch)
[285]1851 }
[675]1852 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
[222]1853 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
[89]1854 }
1855 }
[146]1856 case "PART":
1857 var namesStr string
1858 if err := parseMessageParams(msg, &namesStr); err != nil {
1859 return err
1860 }
1861
1862 var reason string
1863 if len(msg.Params) > 1 {
1864 reason = msg.Params[1]
1865 }
1866
1867 for _, name := range strings.Split(namesStr, ",") {
1868 uc, upstreamName, err := dc.unmarshalEntity(name)
1869 if err != nil {
[158]1870 return err
[146]1871 }
1872
[284]1873 if strings.EqualFold(reason, "detach") {
[478]1874 ch := uc.network.channels.Value(upstreamName)
1875 if ch != nil {
[435]1876 uc.network.detach(ch)
1877 } else {
1878 ch = &Channel{
1879 Name: name,
1880 Detached: true,
1881 }
[478]1882 uc.network.channels.SetValue(upstreamName, ch)
[284]1883 }
[675]1884 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
[435]1885 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1886 }
[284]1887 } else {
1888 params := []string{upstreamName}
1889 if reason != "" {
1890 params = append(params, reason)
1891 }
[757]1892 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[284]1893 Command: "PART",
1894 Params: params,
1895 })
[146]1896
[676]1897 if err := uc.network.deleteChannel(ctx, upstreamName); err != nil {
[284]1898 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1899 }
[146]1900 }
1901 }
[159]1902 case "KICK":
1903 var channelStr, userStr string
1904 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1905 return err
1906 }
1907
1908 channels := strings.Split(channelStr, ",")
1909 users := strings.Split(userStr, ",")
1910
1911 var reason string
1912 if len(msg.Params) > 2 {
1913 reason = msg.Params[2]
1914 }
1915
1916 if len(channels) != 1 && len(channels) != len(users) {
1917 return ircError{&irc.Message{
1918 Command: irc.ERR_BADCHANMASK,
1919 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1920 }}
1921 }
1922
1923 for i, user := range users {
1924 var channel string
1925 if len(channels) == 1 {
1926 channel = channels[0]
1927 } else {
1928 channel = channels[i]
1929 }
1930
1931 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1932 if err != nil {
1933 return err
1934 }
1935
1936 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1937 if err != nil {
1938 return err
1939 }
1940
1941 if ucChannel != ucUser {
1942 return ircError{&irc.Message{
1943 Command: irc.ERR_USERNOTINCHANNEL,
[400]1944 Params: []string{dc.nick, user, channel, "They are on another network"},
[159]1945 }}
1946 }
1947 uc := ucChannel
1948
1949 params := []string{upstreamChannel, upstreamUser}
1950 if reason != "" {
1951 params = append(params, reason)
1952 }
[757]1953 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[159]1954 Command: "KICK",
1955 Params: params,
1956 })
1957 }
[69]1958 case "MODE":
[46]1959 var name string
1960 if err := parseMessageParams(msg, &name); err != nil {
1961 return err
1962 }
1963
1964 var modeStr string
1965 if len(msg.Params) > 1 {
1966 modeStr = msg.Params[1]
1967 }
1968
[478]1969 if casemapASCII(name) == dc.nickCM {
[46]1970 if modeStr != "" {
[554]1971 if uc := dc.upstream(); uc != nil {
[757]1972 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[69]1973 Command: "MODE",
1974 Params: []string{uc.nick, modeStr},
1975 })
[554]1976 } else {
1977 dc.SendMessage(&irc.Message{
1978 Prefix: dc.srv.prefix(),
1979 Command: irc.ERR_UMODEUNKNOWNFLAG,
1980 Params: []string{dc.nick, "Cannot change user mode in multi-upstream mode"},
1981 })
1982 }
[46]1983 } else {
[553]1984 var userMode string
1985 if uc := dc.upstream(); uc != nil {
1986 userMode = string(uc.modes)
1987 }
1988
[55]1989 dc.SendMessage(&irc.Message{
1990 Prefix: dc.srv.prefix(),
[46]1991 Command: irc.RPL_UMODEIS,
[672]1992 Params: []string{dc.nick, "+" + userMode},
[54]1993 })
[46]1994 }
[139]1995 return nil
[46]1996 }
[139]1997
1998 uc, upstreamName, err := dc.unmarshalEntity(name)
1999 if err != nil {
2000 return err
2001 }
2002
2003 if !uc.isChannel(upstreamName) {
2004 return ircError{&irc.Message{
2005 Command: irc.ERR_USERSDONTMATCH,
2006 Params: []string{dc.nick, "Cannot change mode for other users"},
2007 }}
2008 }
2009
2010 if modeStr != "" {
2011 params := []string{upstreamName, modeStr}
2012 params = append(params, msg.Params[2:]...)
[757]2013 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[139]2014 Command: "MODE",
2015 Params: params,
2016 })
2017 } else {
[478]2018 ch := uc.channels.Value(upstreamName)
2019 if ch == nil {
[139]2020 return ircError{&irc.Message{
2021 Command: irc.ERR_NOSUCHCHANNEL,
2022 Params: []string{dc.nick, name, "No such channel"},
2023 }}
2024 }
2025
2026 if ch.modes == nil {
2027 // we haven't received the initial RPL_CHANNELMODEIS yet
2028 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
2029 return nil
2030 }
2031
2032 modeStr, modeParams := ch.modes.Format()
2033 params := []string{dc.nick, name, modeStr}
2034 params = append(params, modeParams...)
2035
2036 dc.SendMessage(&irc.Message{
2037 Prefix: dc.srv.prefix(),
2038 Command: irc.RPL_CHANNELMODEIS,
2039 Params: params,
2040 })
[162]2041 if ch.creationTime != "" {
2042 dc.SendMessage(&irc.Message{
2043 Prefix: dc.srv.prefix(),
2044 Command: rpl_creationtime,
2045 Params: []string{dc.nick, name, ch.creationTime},
2046 })
2047 }
[139]2048 }
[160]2049 case "TOPIC":
2050 var channel string
2051 if err := parseMessageParams(msg, &channel); err != nil {
2052 return err
2053 }
2054
[478]2055 uc, upstreamName, err := dc.unmarshalEntity(channel)
[160]2056 if err != nil {
2057 return err
2058 }
2059
2060 if len(msg.Params) > 1 { // setting topic
2061 topic := msg.Params[1]
[757]2062 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[160]2063 Command: "TOPIC",
[478]2064 Params: []string{upstreamName, topic},
[160]2065 })
2066 } else { // getting topic
[478]2067 ch := uc.channels.Value(upstreamName)
2068 if ch == nil {
[160]2069 return ircError{&irc.Message{
2070 Command: irc.ERR_NOSUCHCHANNEL,
[478]2071 Params: []string{dc.nick, upstreamName, "No such channel"},
[160]2072 }}
2073 }
2074 sendTopic(dc, ch)
2075 }
[177]2076 case "LIST":
[681]2077 network := dc.network
2078 if network == nil && len(msg.Params) > 0 {
2079 var err error
2080 network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0])
2081 if err != nil {
2082 return err
[177]2083 }
2084 }
[681]2085 if network == nil {
2086 dc.SendMessage(&irc.Message{
2087 Prefix: dc.srv.prefix(),
2088 Command: irc.RPL_LISTEND,
2089 Params: []string{dc.nick, "LIST without a network suffix is not supported in multi-upstream mode"},
2090 })
2091 return nil
2092 }
[177]2093
[681]2094 uc := network.conn
2095 if uc == nil {
2096 dc.SendMessage(&irc.Message{
2097 Prefix: dc.srv.prefix(),
2098 Command: irc.RPL_LISTEND,
2099 Params: []string{dc.nick, "Disconnected from upstream server"},
2100 })
2101 return nil
2102 }
2103
[682]2104 uc.enqueueCommand(dc, msg)
[140]2105 case "NAMES":
2106 if len(msg.Params) == 0 {
2107 dc.SendMessage(&irc.Message{
2108 Prefix: dc.srv.prefix(),
2109 Command: irc.RPL_ENDOFNAMES,
2110 Params: []string{dc.nick, "*", "End of /NAMES list"},
2111 })
2112 return nil
2113 }
2114
2115 channels := strings.Split(msg.Params[0], ",")
2116 for _, channel := range channels {
[478]2117 uc, upstreamName, err := dc.unmarshalEntity(channel)
[140]2118 if err != nil {
2119 return err
2120 }
2121
[478]2122 ch := uc.channels.Value(upstreamName)
2123 if ch != nil {
[140]2124 sendNames(dc, ch)
2125 } else {
2126 // NAMES on a channel we have not joined, ask upstream
[757]2127 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[140]2128 Command: "NAMES",
[478]2129 Params: []string{upstreamName},
[140]2130 })
2131 }
2132 }
[660]2133 // For WHOX docs, see:
2134 // - http://faerion.sourceforge.net/doc/irc/whox.var
2135 // - https://github.com/quakenet/snircd/blob/master/doc/readme.who
2136 // Note, many features aren't widely implemented, such as flags and mask2
[127]2137 case "WHO":
2138 if len(msg.Params) == 0 {
2139 // TODO: support WHO without parameters
2140 dc.SendMessage(&irc.Message{
2141 Prefix: dc.srv.prefix(),
2142 Command: irc.RPL_ENDOFWHO,
[140]2143 Params: []string{dc.nick, "*", "End of /WHO list"},
[127]2144 })
2145 return nil
2146 }
2147
[660]2148 // Clients will use the first mask to match RPL_ENDOFWHO
2149 endOfWhoToken := msg.Params[0]
[127]2150
[660]2151 // TODO: add support for WHOX mask2
2152 mask := msg.Params[0]
2153 var options string
2154 if len(msg.Params) > 1 {
2155 options = msg.Params[1]
2156 }
2157
2158 optionsParts := strings.SplitN(options, "%", 2)
2159 // TODO: add support for WHOX flags in optionsParts[0]
2160 var fields, whoxToken string
2161 if len(optionsParts) == 2 {
2162 optionsParts := strings.SplitN(optionsParts[1], ",", 2)
2163 fields = strings.ToLower(optionsParts[0])
2164 if len(optionsParts) == 2 && strings.Contains(fields, "t") {
2165 whoxToken = optionsParts[1]
2166 }
2167 }
2168
2169 // TODO: support mixed bouncer/upstream WHO queries
2170 maskCM := casemapASCII(mask)
2171 if dc.network == nil && maskCM == dc.nickCM {
[142]2172 // TODO: support AWAY (H/G) in self WHO reply
[658]2173 flags := "H"
2174 if dc.user.Admin {
[659]2175 flags += "*"
[658]2176 }
[660]2177 info := whoxInfo{
2178 Token: whoxToken,
2179 Username: dc.user.Username,
2180 Hostname: dc.hostname,
[691]2181 Server: dc.srv.Config().Hostname,
[660]2182 Nickname: dc.nick,
2183 Flags: flags,
[661]2184 Account: dc.user.Username,
[660]2185 Realname: dc.realname,
2186 }
2187 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
[142]2188 dc.SendMessage(&irc.Message{
2189 Prefix: dc.srv.prefix(),
2190 Command: irc.RPL_ENDOFWHO,
[660]2191 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
[142]2192 })
2193 return nil
2194 }
[660]2195 if maskCM == serviceNickCM {
2196 info := whoxInfo{
2197 Token: whoxToken,
2198 Username: servicePrefix.User,
2199 Hostname: servicePrefix.Host,
[691]2200 Server: dc.srv.Config().Hostname,
[660]2201 Nickname: serviceNick,
2202 Flags: "H*",
[661]2203 Account: serviceNick,
[660]2204 Realname: serviceRealname,
2205 }
2206 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
[343]2207 dc.SendMessage(&irc.Message{
2208 Prefix: dc.srv.prefix(),
2209 Command: irc.RPL_ENDOFWHO,
[660]2210 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
[343]2211 })
2212 return nil
2213 }
[142]2214
[660]2215 // TODO: properly support WHO masks
2216 uc, upstreamMask, err := dc.unmarshalEntity(mask)
[127]2217 if err != nil {
2218 return err
2219 }
2220
[660]2221 params := []string{upstreamMask}
2222 if options != "" {
2223 params = append(params, options)
[127]2224 }
2225
[682]2226 uc.enqueueCommand(dc, &irc.Message{
[127]2227 Command: "WHO",
2228 Params: params,
2229 })
[128]2230 case "WHOIS":
2231 if len(msg.Params) == 0 {
2232 return ircError{&irc.Message{
2233 Command: irc.ERR_NONICKNAMEGIVEN,
2234 Params: []string{dc.nick, "No nickname given"},
2235 }}
2236 }
2237
2238 var target, mask string
2239 if len(msg.Params) == 1 {
2240 target = ""
2241 mask = msg.Params[0]
2242 } else {
2243 target = msg.Params[0]
2244 mask = msg.Params[1]
2245 }
2246 // TODO: support multiple WHOIS users
2247 if i := strings.IndexByte(mask, ','); i >= 0 {
2248 mask = mask[:i]
2249 }
2250
[520]2251 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
[142]2252 dc.SendMessage(&irc.Message{
2253 Prefix: dc.srv.prefix(),
2254 Command: irc.RPL_WHOISUSER,
[184]2255 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
[142]2256 })
2257 dc.SendMessage(&irc.Message{
2258 Prefix: dc.srv.prefix(),
2259 Command: irc.RPL_WHOISSERVER,
[804]2260 Params: []string{dc.nick, dc.nick, dc.srv.Config().Hostname, "suika"},
[142]2261 })
[658]2262 if dc.user.Admin {
2263 dc.SendMessage(&irc.Message{
2264 Prefix: dc.srv.prefix(),
2265 Command: irc.RPL_WHOISOPERATOR,
2266 Params: []string{dc.nick, dc.nick, "is a bouncer administrator"},
2267 })
2268 }
[142]2269 dc.SendMessage(&irc.Message{
2270 Prefix: dc.srv.prefix(),
[661]2271 Command: rpl_whoisaccount,
2272 Params: []string{dc.nick, dc.nick, dc.user.Username, "is logged in as"},
2273 })
2274 dc.SendMessage(&irc.Message{
2275 Prefix: dc.srv.prefix(),
[142]2276 Command: irc.RPL_ENDOFWHOIS,
2277 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
2278 })
2279 return nil
2280 }
[609]2281 if casemapASCII(mask) == serviceNickCM {
2282 dc.SendMessage(&irc.Message{
2283 Prefix: dc.srv.prefix(),
2284 Command: irc.RPL_WHOISUSER,
2285 Params: []string{dc.nick, serviceNick, servicePrefix.User, servicePrefix.Host, "*", serviceRealname},
2286 })
2287 dc.SendMessage(&irc.Message{
2288 Prefix: dc.srv.prefix(),
2289 Command: irc.RPL_WHOISSERVER,
[804]2290 Params: []string{dc.nick, serviceNick, dc.srv.Config().Hostname, "suika"},
[609]2291 })
2292 dc.SendMessage(&irc.Message{
2293 Prefix: dc.srv.prefix(),
[657]2294 Command: irc.RPL_WHOISOPERATOR,
2295 Params: []string{dc.nick, serviceNick, "is the bouncer service"},
2296 })
2297 dc.SendMessage(&irc.Message{
2298 Prefix: dc.srv.prefix(),
[661]2299 Command: rpl_whoisaccount,
2300 Params: []string{dc.nick, serviceNick, serviceNick, "is logged in as"},
2301 })
2302 dc.SendMessage(&irc.Message{
2303 Prefix: dc.srv.prefix(),
[609]2304 Command: irc.RPL_ENDOFWHOIS,
2305 Params: []string{dc.nick, serviceNick, "End of /WHOIS list"},
2306 })
2307 return nil
2308 }
[142]2309
[128]2310 // TODO: support WHOIS masks
2311 uc, upstreamNick, err := dc.unmarshalEntity(mask)
2312 if err != nil {
2313 return err
2314 }
2315
2316 var params []string
2317 if target != "" {
[299]2318 if target == mask { // WHOIS nick nick
2319 params = []string{upstreamNick, upstreamNick}
2320 } else {
2321 params = []string{target, upstreamNick}
2322 }
[128]2323 } else {
2324 params = []string{upstreamNick}
2325 }
2326
[757]2327 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[128]2328 Command: "WHOIS",
2329 Params: params,
2330 })
[562]2331 case "PRIVMSG", "NOTICE":
[58]2332 var targetsStr, text string
2333 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
2334 return err
2335 }
[303]2336 tags := copyClientTags(msg.Tags)
[58]2337
2338 for _, name := range strings.Split(targetsStr, ",") {
[691]2339 if name == "$"+dc.srv.Config().Hostname || (name == "$*" && dc.network == nil) {
[563]2340 // "$" means a server mask follows. If it's the bouncer's
2341 // hostname, broadcast the message to all bouncer users.
2342 if !dc.user.Admin {
2343 return ircError{&irc.Message{
2344 Prefix: dc.srv.prefix(),
2345 Command: irc.ERR_BADMASK,
2346 Params: []string{dc.nick, name, "Permission denied to broadcast message to all bouncer users"},
2347 }}
2348 }
2349
2350 dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
2351
2352 broadcastTags := tags.Copy()
[784]2353 broadcastTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[563]2354 broadcastMsg := &irc.Message{
2355 Tags: broadcastTags,
2356 Prefix: servicePrefix,
2357 Command: msg.Command,
2358 Params: []string{name, text},
2359 }
2360 dc.srv.forEachUser(func(u *user) {
2361 u.events <- eventBroadcast{broadcastMsg}
2362 })
2363 continue
2364 }
2365
[529]2366 if dc.network == nil && casemapASCII(name) == dc.nickCM {
[618]2367 dc.SendMessage(&irc.Message{
2368 Tags: msg.Tags.Copy(),
2369 Prefix: dc.prefix(),
2370 Command: msg.Command,
2371 Params: []string{name, text},
2372 })
[529]2373 continue
2374 }
2375
[562]2376 if msg.Command == "PRIVMSG" && casemapASCII(name) == serviceNickCM {
[431]2377 if dc.caps["echo-message"] {
2378 echoTags := tags.Copy()
[784]2379 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[431]2380 dc.SendMessage(&irc.Message{
2381 Tags: echoTags,
2382 Prefix: dc.prefix(),
[562]2383 Command: msg.Command,
[431]2384 Params: []string{name, text},
2385 })
2386 }
[677]2387 handleServicePRIVMSG(ctx, dc, text)
[117]2388 continue
2389 }
2390
[127]2391 uc, upstreamName, err := dc.unmarshalEntity(name)
[58]2392 if err != nil {
2393 return err
2394 }
2395
[562]2396 if msg.Command == "PRIVMSG" && uc.network.casemap(upstreamName) == "nickserv" {
[675]2397 dc.handleNickServPRIVMSG(ctx, uc, text)
[95]2398 }
2399
[268]2400 unmarshaledText := text
2401 if uc.isChannel(upstreamName) {
2402 unmarshaledText = dc.unmarshalText(uc, text)
2403 }
[757]2404 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[303]2405 Tags: tags,
[562]2406 Command: msg.Command,
[268]2407 Params: []string{upstreamName, unmarshaledText},
[60]2408 })
[105]2409
[303]2410 echoTags := tags.Copy()
[784]2411 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[559]2412 if uc.account != "" {
2413 echoTags["account"] = irc.TagValue(uc.account)
2414 }
[113]2415 echoMsg := &irc.Message{
[690]2416 Tags: echoTags,
2417 Prefix: &irc.Prefix{Name: uc.nick},
[562]2418 Command: msg.Command,
[113]2419 Params: []string{upstreamName, text},
2420 }
[239]2421 uc.produce(upstreamName, echoMsg, dc)
[435]2422
2423 uc.updateChannelAutoDetach(upstreamName)
[58]2424 }
[303]2425 case "TAGMSG":
2426 var targetsStr string
2427 if err := parseMessageParams(msg, &targetsStr); err != nil {
2428 return err
2429 }
2430 tags := copyClientTags(msg.Tags)
2431
2432 for _, name := range strings.Split(targetsStr, ",") {
[617]2433 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2434 dc.SendMessage(&irc.Message{
2435 Tags: msg.Tags.Copy(),
2436 Prefix: dc.prefix(),
2437 Command: "TAGMSG",
2438 Params: []string{name},
2439 })
2440 continue
2441 }
2442
[616]2443 if casemapASCII(name) == serviceNickCM {
2444 continue
2445 }
2446
[303]2447 uc, upstreamName, err := dc.unmarshalEntity(name)
2448 if err != nil {
2449 return err
2450 }
[427]2451 if _, ok := uc.caps["message-tags"]; !ok {
2452 continue
2453 }
[303]2454
[757]2455 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[303]2456 Tags: tags,
2457 Command: "TAGMSG",
2458 Params: []string{upstreamName},
2459 })
[435]2460
[780]2461 echoTags := tags.Copy()
[784]2462 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[780]2463 if uc.account != "" {
2464 echoTags["account"] = irc.TagValue(uc.account)
2465 }
2466 echoMsg := &irc.Message{
2467 Tags: echoTags,
2468 Prefix: &irc.Prefix{Name: uc.nick},
2469 Command: "TAGMSG",
2470 Params: []string{upstreamName},
2471 }
2472 uc.produce(upstreamName, echoMsg, dc)
2473
[435]2474 uc.updateChannelAutoDetach(upstreamName)
[303]2475 }
[163]2476 case "INVITE":
2477 var user, channel string
2478 if err := parseMessageParams(msg, &user, &channel); err != nil {
2479 return err
2480 }
2481
2482 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2483 if err != nil {
2484 return err
2485 }
2486
2487 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2488 if err != nil {
2489 return err
2490 }
2491
2492 if ucChannel != ucUser {
2493 return ircError{&irc.Message{
2494 Command: irc.ERR_USERNOTINCHANNEL,
[401]2495 Params: []string{dc.nick, user, channel, "They are on another network"},
[163]2496 }}
2497 }
2498 uc := ucChannel
2499
[757]2500 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[163]2501 Command: "INVITE",
2502 Params: []string{upstreamUser, upstreamChannel},
2503 })
[724]2504 case "AUTHENTICATE":
2505 // Post-connection-registration AUTHENTICATE is unsupported in
2506 // multi-upstream mode, or if the upstream doesn't support SASL
2507 uc := dc.upstream()
2508 if uc == nil || !uc.caps["sasl"] {
2509 return ircError{&irc.Message{
2510 Command: irc.ERR_SASLFAIL,
2511 Params: []string{dc.nick, "Upstream network authentication not supported"},
2512 }}
2513 }
2514
2515 credentials, err := dc.handleAuthenticateCommand(msg)
2516 if err != nil {
2517 return err
2518 }
2519
2520 if credentials != nil {
2521 if uc.saslClient != nil {
2522 dc.endSASL(&irc.Message{
2523 Prefix: dc.srv.prefix(),
2524 Command: irc.ERR_SASLFAIL,
2525 Params: []string{dc.nick, "Another authentication attempt is already in progress"},
2526 })
2527 return nil
2528 }
2529
2530 uc.logger.Printf("starting post-registration SASL PLAIN authentication with username %q", credentials.plainUsername)
2531 uc.saslClient = sasl.NewPlainClient("", credentials.plainUsername, credentials.plainPassword)
2532 uc.enqueueCommand(dc, &irc.Message{
2533 Command: "AUTHENTICATE",
2534 Params: []string{"PLAIN"},
2535 })
2536 }
[729]2537 case "REGISTER", "VERIFY":
2538 // Check number of params here, since we'll use that to save the
2539 // credentials on command success
2540 if (msg.Command == "REGISTER" && len(msg.Params) < 3) || (msg.Command == "VERIFY" && len(msg.Params) < 2) {
2541 return newNeedMoreParamsError(msg.Command)
2542 }
2543
2544 uc := dc.upstream()
2545 if uc == nil || !uc.caps["draft/account-registration"] {
2546 return ircError{&irc.Message{
2547 Command: "FAIL",
2548 Params: []string{msg.Command, "TEMPORARILY_UNAVAILABLE", "*", "Upstream network account registration not supported"},
2549 }}
2550 }
2551
2552 uc.logger.Printf("starting %v with account name %v", msg.Command, msg.Params[0])
2553 uc.enqueueCommand(dc, msg)
[684]2554 case "MONITOR":
2555 // MONITOR is unsupported in multi-upstream mode
2556 uc := dc.upstream()
2557 if uc == nil {
2558 return newUnknownCommandError(msg.Command)
2559 }
[742]2560 if _, ok := uc.isupport["MONITOR"]; !ok {
2561 return newUnknownCommandError(msg.Command)
2562 }
[684]2563
2564 var subcommand string
2565 if err := parseMessageParams(msg, &subcommand); err != nil {
2566 return err
2567 }
2568
2569 switch strings.ToUpper(subcommand) {
2570 case "+", "-":
2571 var targets string
2572 if err := parseMessageParams(msg, nil, &targets); err != nil {
2573 return err
2574 }
2575 for _, target := range strings.Split(targets, ",") {
2576 if subcommand == "+" {
2577 // Hard limit, just to avoid having downstreams fill our map
2578 if len(dc.monitored.innerMap) >= 1000 {
2579 dc.SendMessage(&irc.Message{
2580 Prefix: dc.srv.prefix(),
2581 Command: irc.ERR_MONLISTFULL,
2582 Params: []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
2583 })
2584 continue
2585 }
2586
2587 dc.monitored.SetValue(target, nil)
2588
2589 if uc.monitored.Has(target) {
2590 cmd := irc.RPL_MONOFFLINE
2591 if online := uc.monitored.Value(target); online {
2592 cmd = irc.RPL_MONONLINE
2593 }
2594
2595 dc.SendMessage(&irc.Message{
2596 Prefix: dc.srv.prefix(),
2597 Command: cmd,
2598 Params: []string{dc.nick, target},
2599 })
2600 }
2601 } else {
2602 dc.monitored.Delete(target)
2603 }
2604 }
2605 uc.updateMonitor()
2606 case "C": // clear
2607 dc.monitored = newCasemapMap(0)
2608 uc.updateMonitor()
2609 case "L": // list
2610 // TODO: be less lazy and pack the list
2611 for _, entry := range dc.monitored.innerMap {
2612 dc.SendMessage(&irc.Message{
2613 Prefix: dc.srv.prefix(),
2614 Command: irc.RPL_MONLIST,
2615 Params: []string{dc.nick, entry.originalKey},
2616 })
2617 }
2618 dc.SendMessage(&irc.Message{
2619 Prefix: dc.srv.prefix(),
2620 Command: irc.RPL_ENDOFMONLIST,
2621 Params: []string{dc.nick, "End of MONITOR list"},
2622 })
2623 case "S": // status
2624 // TODO: be less lazy and pack the lists
2625 for _, entry := range dc.monitored.innerMap {
2626 target := entry.originalKey
2627
2628 cmd := irc.RPL_MONOFFLINE
2629 if online := uc.monitored.Value(target); online {
2630 cmd = irc.RPL_MONONLINE
2631 }
2632
2633 dc.SendMessage(&irc.Message{
2634 Prefix: dc.srv.prefix(),
2635 Command: cmd,
2636 Params: []string{dc.nick, target},
2637 })
2638 }
2639 }
[319]2640 case "CHATHISTORY":
2641 var subcommand string
2642 if err := parseMessageParams(msg, &subcommand); err != nil {
2643 return err
2644 }
[516]2645 var target, limitStr string
2646 var boundsStr [2]string
2647 switch subcommand {
[719]2648 case "AFTER", "BEFORE", "LATEST":
[516]2649 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2650 return err
2651 }
2652 case "BETWEEN":
2653 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2654 return err
2655 }
[549]2656 case "TARGETS":
[688]2657 if dc.network == nil {
2658 // Either an unbound bouncer network, in which case we should return no targets,
2659 // or a multi-upstream downstream, but we don't support CHATHISTORY TARGETS for those yet.
2660 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {})
2661 return nil
2662 }
[549]2663 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2664 return err
2665 }
[516]2666 default:
[719]2667 // TODO: support AROUND
[319]2668 return ircError{&irc.Message{
2669 Command: "FAIL",
[516]2670 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
[319]2671 }}
2672 }
2673
[586]2674 // We don't save history for our service
2675 if casemapASCII(target) == serviceNickCM {
2676 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {})
2677 return nil
2678 }
2679
[441]2680 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2681 if !ok {
[319]2682 return ircError{&irc.Message{
2683 Command: irc.ERR_UNKNOWNCOMMAND,
[456]2684 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
[319]2685 }}
2686 }
2687
[585]2688 network, entity, err := dc.unmarshalEntityNetwork(target)
[319]2689 if err != nil {
2690 return err
2691 }
[585]2692 entity = network.casemap(entity)
[319]2693
2694 // TODO: support msgid criteria
[516]2695 var bounds [2]time.Time
2696 bounds[0] = parseChatHistoryBound(boundsStr[0])
[719]2697 if subcommand == "LATEST" && boundsStr[0] == "*" {
[720]2698 bounds[0] = time.Now()
[719]2699 } else if bounds[0].IsZero() {
[319]2700 return ircError{&irc.Message{
2701 Command: "FAIL",
[516]2702 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
[319]2703 }}
2704 }
2705
[516]2706 if boundsStr[1] != "" {
2707 bounds[1] = parseChatHistoryBound(boundsStr[1])
2708 if bounds[1].IsZero() {
2709 return ircError{&irc.Message{
2710 Command: "FAIL",
2711 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2712 }}
2713 }
[319]2714 }
2715
2716 limit, err := strconv.Atoi(limitStr)
[670]2717 if err != nil || limit < 0 || limit > chatHistoryLimit {
[319]2718 return ircError{&irc.Message{
2719 Command: "FAIL",
[456]2720 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
[319]2721 }}
2722 }
2723
[665]2724 eventPlayback := dc.caps["draft/event-playback"]
2725
[387]2726 var history []*irc.Message
[319]2727 switch subcommand {
[719]2728 case "BEFORE", "LATEST":
[667]2729 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], time.Time{}, limit, eventPlayback)
[360]2730 case "AFTER":
[667]2731 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], time.Now(), limit, eventPlayback)
[516]2732 case "BETWEEN":
2733 if bounds[0].Before(bounds[1]) {
[667]2734 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
[516]2735 } else {
[667]2736 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
[516]2737 }
[549]2738 case "TARGETS":
2739 // TODO: support TARGETS in multi-upstream mode
[667]2740 targets, err := store.ListTargets(ctx, &network.Network, bounds[0], bounds[1], limit, eventPlayback)
[549]2741 if err != nil {
[627]2742 dc.logger.Printf("failed fetching targets for chathistory: %v", err)
[549]2743 return ircError{&irc.Message{
2744 Command: "FAIL",
2745 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2746 }}
2747 }
2748
[551]2749 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2750 for _, target := range targets {
[585]2751 if ch := network.channels.Value(target.Name); ch != nil && ch.Detached {
[551]2752 continue
2753 }
[549]2754
[551]2755 dc.SendMessage(&irc.Message{
2756 Tags: irc.Tags{"batch": batchRef},
2757 Prefix: dc.srv.prefix(),
2758 Command: "CHATHISTORY",
[784]2759 Params: []string{"TARGETS", target.Name, formatServerTime(target.LatestMessage)},
[551]2760 })
[550]2761 }
[549]2762 })
2763
2764 return nil
[319]2765 }
[387]2766 if err != nil {
[515]2767 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
[387]2768 return newChatHistoryError(subcommand, target)
2769 }
2770
[551]2771 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2772 for _, msg := range history {
2773 msg.Tags["batch"] = batchRef
[585]2774 dc.SendMessage(dc.marshalMessage(msg, network))
[551]2775 }
[387]2776 })
[781]2777 case "READ":
2778 var target, criteria string
2779 if err := parseMessageParams(msg, &target); err != nil {
2780 return ircError{&irc.Message{
2781 Command: "FAIL",
2782 Params: []string{"READ", "NEED_MORE_PARAMS", "Missing parameters"},
2783 }}
2784 }
2785 if len(msg.Params) > 1 {
2786 criteria = msg.Params[1]
2787 }
2788
[783]2789 // We don't save read receipts for our service
2790 if casemapASCII(target) == serviceNickCM {
2791 dc.SendMessage(&irc.Message{
2792 Prefix: dc.prefix(),
2793 Command: "READ",
2794 Params: []string{target, "*"},
2795 })
2796 return nil
2797 }
2798
[781]2799 uc, entity, err := dc.unmarshalEntity(target)
2800 if err != nil {
2801 return err
2802 }
2803 entityCM := uc.network.casemap(entity)
2804
2805 r, err := dc.srv.db.GetReadReceipt(ctx, uc.network.ID, entityCM)
2806 if err != nil {
2807 dc.logger.Printf("failed to get the read receipt for %q: %v", entity, err)
2808 return ircError{&irc.Message{
2809 Command: "FAIL",
2810 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2811 }}
2812 } else if r == nil {
2813 r = &ReadReceipt{
2814 Target: entityCM,
2815 }
2816 }
2817
2818 broadcast := false
2819 if len(criteria) > 0 {
2820 // TODO: support msgid criteria
2821 criteriaParts := strings.SplitN(criteria, "=", 2)
2822 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
2823 return ircError{&irc.Message{
2824 Command: "FAIL",
2825 Params: []string{"READ", "INVALID_PARAMS", criteria, "Unknown criteria"},
2826 }}
2827 }
2828
2829 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
2830 if err != nil {
2831 return ircError{&irc.Message{
2832 Command: "FAIL",
2833 Params: []string{"READ", "INVALID_PARAMS", criteria, "Invalid criteria"},
2834 }}
2835 }
2836 now := time.Now()
2837 if timestamp.After(now) {
2838 timestamp = now
2839 }
2840 if r.Timestamp.Before(timestamp) {
2841 r.Timestamp = timestamp
2842 if err := dc.srv.db.StoreReadReceipt(ctx, uc.network.ID, r); err != nil {
2843 dc.logger.Printf("failed to store receipt for %q: %v", entity, err)
2844 return ircError{&irc.Message{
2845 Command: "FAIL",
2846 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2847 }}
2848 }
2849 broadcast = true
2850 }
2851 }
2852
2853 timestampStr := "*"
2854 if !r.Timestamp.IsZero() {
[784]2855 timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
[781]2856 }
2857 uc.forEachDownstream(func(d *downstreamConn) {
2858 if broadcast || dc.id == d.id {
2859 d.SendMessage(&irc.Message{
2860 Prefix: d.prefix(),
2861 Command: "READ",
2862 Params: []string{d.marshalEntity(uc.network, entity), timestampStr},
2863 })
2864 }
2865 })
[532]2866 case "BOUNCER":
2867 var subcommand string
2868 if err := parseMessageParams(msg, &subcommand); err != nil {
2869 return err
2870 }
2871
2872 switch strings.ToUpper(subcommand) {
[646]2873 case "BIND":
2874 return ircError{&irc.Message{
2875 Command: "FAIL",
2876 Params: []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind to a network after registration"},
2877 }}
[532]2878 case "LISTNETWORKS":
[551]2879 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
[768]2880 for _, network := range dc.user.networks {
[551]2881 idStr := fmt.Sprintf("%v", network.ID)
2882 attrs := getNetworkAttrs(network)
2883 dc.SendMessage(&irc.Message{
2884 Tags: irc.Tags{"batch": batchRef},
2885 Prefix: dc.srv.prefix(),
2886 Command: "BOUNCER",
2887 Params: []string{"NETWORK", idStr, attrs.String()},
2888 })
[768]2889 }
[532]2890 })
2891 case "ADDNETWORK":
2892 var attrsStr string
2893 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2894 return err
2895 }
2896 attrs := irc.ParseTags(attrsStr)
2897
[654]2898 record := &Network{Nick: dc.nick, Enabled: true}
2899 if err := updateNetworkAttrs(record, attrs, subcommand); err != nil {
2900 return err
[532]2901 }
2902
[664]2903 if record.Nick == dc.user.Username {
2904 record.Nick = ""
2905 }
[654]2906 if record.Realname == dc.user.Realname {
2907 record.Realname = ""
[532]2908 }
2909
[676]2910 network, err := dc.user.createNetwork(ctx, record)
[532]2911 if err != nil {
2912 return ircError{&irc.Message{
2913 Command: "FAIL",
2914 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2915 }}
2916 }
2917
2918 dc.SendMessage(&irc.Message{
2919 Prefix: dc.srv.prefix(),
2920 Command: "BOUNCER",
2921 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2922 })
2923 case "CHANGENETWORK":
2924 var idStr, attrsStr string
2925 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2926 return err
2927 }
[535]2928 id, err := parseBouncerNetID(subcommand, idStr)
[532]2929 if err != nil {
2930 return err
2931 }
2932 attrs := irc.ParseTags(attrsStr)
2933
2934 net := dc.user.getNetworkByID(id)
2935 if net == nil {
2936 return ircError{&irc.Message{
2937 Command: "FAIL",
[535]2938 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
[532]2939 }}
2940 }
2941
2942 record := net.Network // copy network record because we'll mutate it
[654]2943 if err := updateNetworkAttrs(&record, attrs, subcommand); err != nil {
2944 return err
[532]2945 }
2946
[664]2947 if record.Nick == dc.user.Username {
2948 record.Nick = ""
2949 }
[654]2950 if record.Realname == dc.user.Realname {
2951 record.Realname = ""
2952 }
2953
[676]2954 _, err = dc.user.updateNetwork(ctx, &record)
[532]2955 if err != nil {
2956 return ircError{&irc.Message{
2957 Command: "FAIL",
2958 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2959 }}
2960 }
2961
2962 dc.SendMessage(&irc.Message{
2963 Prefix: dc.srv.prefix(),
2964 Command: "BOUNCER",
2965 Params: []string{"CHANGENETWORK", idStr},
2966 })
2967 case "DELNETWORK":
2968 var idStr string
2969 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2970 return err
2971 }
[535]2972 id, err := parseBouncerNetID(subcommand, idStr)
[532]2973 if err != nil {
2974 return err
2975 }
2976
2977 net := dc.user.getNetworkByID(id)
2978 if net == nil {
2979 return ircError{&irc.Message{
2980 Command: "FAIL",
[535]2981 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
[532]2982 }}
2983 }
2984
[676]2985 if err := dc.user.deleteNetwork(ctx, net.ID); err != nil {
[532]2986 return err
2987 }
2988
2989 dc.SendMessage(&irc.Message{
2990 Prefix: dc.srv.prefix(),
2991 Command: "BOUNCER",
2992 Params: []string{"DELNETWORK", idStr},
2993 })
2994 default:
2995 return ircError{&irc.Message{
2996 Command: "FAIL",
2997 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2998 }}
2999 }
[13]3000 default:
[55]3001 dc.logger.Printf("unhandled message: %v", msg)
[547]3002
3003 // Only forward unknown commands in single-upstream mode
3004 uc := dc.upstream()
3005 if uc == nil {
3006 return newUnknownCommandError(msg.Command)
3007 }
3008
[757]3009 uc.SendMessageLabeled(ctx, dc.id, msg)
[13]3010 }
[42]3011 return nil
[13]3012}
[95]3013
[675]3014func (dc *downstreamConn) handleNickServPRIVMSG(ctx context.Context, uc *upstreamConn, text string) {
[95]3015 username, password, ok := parseNickServCredentials(text, uc.nick)
[724]3016 if ok {
3017 uc.network.autoSaveSASLPlain(ctx, username, password)
[95]3018 }
3019}
3020
3021func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
3022 fields := strings.Fields(text)
3023 if len(fields) < 2 {
3024 return "", "", false
3025 }
3026 cmd := strings.ToUpper(fields[0])
3027 params := fields[1:]
3028 switch cmd {
3029 case "REGISTER":
3030 username = nick
3031 password = params[0]
3032 case "IDENTIFY":
3033 if len(params) == 1 {
3034 username = nick
[182]3035 password = params[0]
[95]3036 } else {
3037 username = params[0]
[182]3038 password = params[1]
[95]3039 }
[182]3040 case "SET":
3041 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
3042 username = nick
3043 password = params[1]
3044 }
[340]3045 default:
3046 return "", "", false
[95]3047 }
3048 return username, password, true
3049}
Note: See TracBrowser for help on using the repository browser.