source: code/trunk/downstream.go@ 559

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

Add support for account-tag

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