source: code/trunk/downstream.go@ 558

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

Don't suggest users to /motd in multi-upstream mode

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