source: code/trunk/downstream.go@ 789

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

downstream: be less strict when picking up client name

Allow e.g. the SASL username to contain "username/network" and the
raw username to contain "username@client", for instance.

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