source: code/trunk/downstream.go@ 697

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

Add config option to globally disable multi-upstream mode

Closes: https://todo.sr.ht/~emersion/soju/122

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