source: code/trunk/downstream.go@ 563

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

Allow admins to broadcast message to all bouncer users

Typically done via:

/notice $<bouncer> <message>

Or, for a connection not bound to a specific network:

/notice $* <message>

The message is broadcast as BouncerServ, because that's the only
user that can be trusted to belong to the bouncer by users. Any
other prefix would conflict with the upstream network.

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