source: code/trunk/downstream.go@ 580

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

Pass-through CLIENTTAGDENY in ISUPPORT

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