source: code/trunk/downstream.go@ 760

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

Add support for downstream multi-line AUTHENTICATE commands

Useful for long passwords.

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