source: code/trunk/downstream.go@ 773

Last change on this file since 773 was 768, checked in by contact, 3 years ago

Drop user.forEachNetwork

It's a trivial for loop.

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