source: code/trunk/downstream.go@ 684

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

Add support for MONITOR

Add support for MONITOR in single-upstream mode.

Each downstream has its own set of monitored targets. These sets
are merged together to compute the MONITOR commands to send to
upstream.

Each upstream has a set of monitored targets accepted by the server
alongside with their status (online/offline). This is used to
directly send replies to downstreams adding a target another
downstream has already added, and send MONITOR S[TATUS] replies.

Co-authored-by: delthas <delthas@…>

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