source: code/trunk/downstream.go@ 608

Last change on this file since 608 was 590, checked in by hubert, 4 years ago

Allow CAP negotiation to happen with CAP REQ

See https://ircv3.net/specs/extensions/capability-negotiation

Upon receiving either a CAP LS or CAP REQ command during connection
registration, the server MUST not complete registration until the
client sends a CAP END command to indicate that capability negotiation
has ended.

This commit should prevent soju from trying to authenticate the user
prior to having received AUTHENTICATE messages, when the client eagerly
requests capabilities with CAP REQ seeing available capabilities
beforehand with CAP LS.

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