source: code/trunk/downstream.go@ 724

Last change on this file since 724 was 724, checked in by contact, 4 years ago

Add support for post-connection-registration upstream SASL auth

Once the downstream connection has logged in with their bouncer
credentials, allow them to issue more SASL auths which will be
redirected to the upstream network. This allows downstream clients
to provide UIs to login to transparently login to upstream networks.

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