source: code/trunk/downstream.go@ 269

Last change on this file since 269 was 268, checked in by delthas, 5 years ago

Unmarshal nicks in texts of PRIVMSG and NOTICE from downstreams

When writing a PRIVMSG or NOTICE on a channel, it is very common to use
autocompletion to mention other users on that channel. When using soju
in multi-network mode, all users will have their nicked suffixed by
/network. This suffix should be removed before sending it upstream.

This adds support for removing all /network suffixes in messages sent
to a channel of that network.

File size: 31.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
[85]48var errAuthFailed = ircError{&irc.Message{
49 Command: irc.ERR_PASSWDMISMATCH,
50 Params: []string{"*", "Invalid username or password"},
51}}
[13]52
53type downstreamConn struct {
[210]54 conn
[22]55
[210]56 id uint64
57
[100]58 registered bool
59 user *user
60 nick string
61 rawUsername string
[168]62 networkName string
[183]63 clientName string
[100]64 realname string
[141]65 hostname string
[100]66 password string // empty after authentication
67 network *network // can be nil
[105]68
[108]69 negociatingCaps bool
70 capVersion int
[236]71 caps map[string]bool
[108]72
[112]73 saslServer sasl.Server
[13]74}
75
[154]76func newDownstreamConn(srv *Server, netConn net.Conn, id uint64) *downstreamConn {
[210]77 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", netConn.RemoteAddr())}
[55]78 dc := &downstreamConn{
[236]79 conn: *newConn(srv, netConn, logger),
80 id: id,
81 caps: make(map[string]bool),
[22]82 }
[141]83 dc.hostname = netConn.RemoteAddr().String()
84 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
85 dc.hostname = host
86 }
[55]87 return dc
[22]88}
89
[55]90func (dc *downstreamConn) prefix() *irc.Prefix {
[27]91 return &irc.Prefix{
[55]92 Name: dc.nick,
[184]93 User: dc.user.Username,
[141]94 Host: dc.hostname,
[27]95 }
96}
97
[90]98func (dc *downstreamConn) forEachNetwork(f func(*network)) {
99 if dc.network != nil {
100 f(dc.network)
101 } else {
102 dc.user.forEachNetwork(f)
103 }
104}
105
[73]106func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
107 dc.user.forEachUpstream(func(uc *upstreamConn) {
[77]108 if dc.network != nil && uc.network != dc.network {
[73]109 return
110 }
111 f(uc)
112 })
113}
114
[89]115// upstream returns the upstream connection, if any. If there are zero or if
116// there are multiple upstream connections, it returns nil.
117func (dc *downstreamConn) upstream() *upstreamConn {
118 if dc.network == nil {
119 return nil
120 }
[136]121 return dc.network.upstream()
[89]122}
123
[260]124func isOurNick(net *network, nick string) bool {
125 // TODO: this doesn't account for nick changes
126 if net.conn != nil {
127 return nick == net.conn.nick
128 }
129 // We're not currently connected to the upstream connection, so we don't
130 // know whether this name is our nickname. Best-effort: use the network's
131 // configured nickname and hope it was the one being used when we were
132 // connected.
133 return nick == net.Nick
134}
135
[249]136// marshalEntity converts an upstream entity name (ie. channel or nick) into a
137// downstream entity name.
138//
139// This involves adding a "/<network>" suffix if the entity isn't the current
140// user.
[260]141func (dc *downstreamConn) marshalEntity(net *network, name string) string {
[257]142 if dc.network != nil {
[260]143 if dc.network != net {
[258]144 panic("soju: tried to marshal an entity for another network")
145 }
[257]146 return name
[119]147 }
[260]148 if isOurNick(net, name) {
[257]149 return dc.nick
150 }
[260]151 return name + "/" + net.GetName()
[119]152}
153
[260]154func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
155 if isOurNick(net, prefix.Name) {
[257]156 return dc.prefix()
157 }
[130]158 if dc.network != nil {
[260]159 if dc.network != net {
[258]160 panic("soju: tried to marshal a user prefix for another network")
161 }
[257]162 return prefix
[119]163 }
[257]164 return &irc.Prefix{
[260]165 Name: prefix.Name + "/" + net.GetName(),
[257]166 User: prefix.User,
167 Host: prefix.Host,
168 }
[119]169}
170
[249]171// unmarshalEntity converts a downstream entity name (ie. channel or nick) into
172// an upstream entity name.
173//
174// This involves removing the "/<network>" suffix.
[127]175func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
[89]176 if uc := dc.upstream(); uc != nil {
177 return uc, name, nil
178 }
179
[127]180 var conn *upstreamConn
[119]181 if i := strings.LastIndexByte(name, '/'); i >= 0 {
[127]182 network := name[i+1:]
[119]183 name = name[:i]
184
185 dc.forEachUpstream(func(uc *upstreamConn) {
186 if network != uc.network.GetName() {
187 return
188 }
189 conn = uc
190 })
191 }
192
[127]193 if conn == nil {
[73]194 return nil, "", ircError{&irc.Message{
195 Command: irc.ERR_NOSUCHCHANNEL,
196 Params: []string{name, "No such channel"},
197 }}
[69]198 }
[127]199 return conn, name, nil
[69]200}
201
[268]202func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
203 if dc.upstream() != nil {
204 return text
205 }
206 // TODO: smarter parsing that ignores URLs
207 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
208}
209
[165]210func (dc *downstreamConn) readMessages(ch chan<- event) error {
[22]211 for {
[210]212 msg, err := dc.ReadMessage()
[22]213 if err == io.EOF {
214 break
215 } else if err != nil {
216 return fmt.Errorf("failed to read IRC command: %v", err)
217 }
218
[165]219 ch <- eventDownstreamMessage{msg, dc}
[22]220 }
221
[45]222 return nil
[22]223}
224
[230]225// SendMessage sends an outgoing message.
226//
227// This can only called from the user goroutine.
[55]228func (dc *downstreamConn) SendMessage(msg *irc.Message) {
[230]229 if !dc.caps["message-tags"] {
[216]230 msg = msg.Copy()
231 for name := range msg.Tags {
232 supported := false
233 switch name {
234 case "time":
[230]235 supported = dc.caps["server-time"]
[216]236 }
237 if !supported {
238 delete(msg.Tags, name)
239 }
240 }
241 }
242
[210]243 dc.conn.SendMessage(msg)
[54]244}
245
[245]246// marshalMessage re-formats a message coming from an upstream connection so
247// that it's suitable for being sent on this downstream connection. Only
248// messages that may appear in logs are supported.
[261]249func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
[227]250 msg = msg.Copy()
[261]251 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
[245]252
[227]253 switch msg.Command {
254 case "PRIVMSG", "NOTICE":
[261]255 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]256 case "NICK":
257 // Nick change for another user
[261]258 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]259 case "JOIN", "PART":
[261]260 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]261 case "KICK":
[261]262 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
263 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
[245]264 case "TOPIC":
[261]265 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]266 case "MODE":
[261]267 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]268 case "QUIT":
[262]269 // This space is intentionally left blank
[227]270 default:
271 panic(fmt.Sprintf("unexpected %q message", msg.Command))
272 }
273
[245]274 return msg
[227]275}
276
[55]277func (dc *downstreamConn) handleMessage(msg *irc.Message) error {
[13]278 switch msg.Command {
[28]279 case "QUIT":
[55]280 return dc.Close()
[13]281 default:
[55]282 if dc.registered {
283 return dc.handleMessageRegistered(msg)
[13]284 } else {
[55]285 return dc.handleMessageUnregistered(msg)
[13]286 }
287 }
288}
289
[55]290func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
[13]291 switch msg.Command {
292 case "NICK":
[117]293 var nick string
294 if err := parseMessageParams(msg, &nick); err != nil {
[43]295 return err
[13]296 }
[117]297 if nick == serviceNick {
298 return ircError{&irc.Message{
299 Command: irc.ERR_NICKNAMEINUSE,
300 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
301 }}
302 }
303 dc.nick = nick
[13]304 case "USER":
[117]305 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
[43]306 return err
[13]307 }
[85]308 case "PASS":
309 if err := parseMessageParams(msg, &dc.password); err != nil {
310 return err
311 }
[108]312 case "CAP":
313 var subCmd string
314 if err := parseMessageParams(msg, &subCmd); err != nil {
315 return err
316 }
317 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
318 return err
319 }
[112]320 case "AUTHENTICATE":
[230]321 if !dc.caps["sasl"] {
[112]322 return ircError{&irc.Message{
[125]323 Command: irc.ERR_SASLFAIL,
[112]324 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
325 }}
326 }
327 if len(msg.Params) == 0 {
328 return ircError{&irc.Message{
[125]329 Command: irc.ERR_SASLFAIL,
[112]330 Params: []string{"*", "Missing AUTHENTICATE argument"},
331 }}
332 }
333 if dc.nick == "" {
334 return ircError{&irc.Message{
[125]335 Command: irc.ERR_SASLFAIL,
[112]336 Params: []string{"*", "Expected NICK command before AUTHENTICATE"},
337 }}
338 }
339
340 var resp []byte
341 if dc.saslServer == nil {
342 mech := strings.ToUpper(msg.Params[0])
343 switch mech {
344 case "PLAIN":
345 dc.saslServer = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
346 return dc.authenticate(username, password)
347 }))
348 default:
349 return ircError{&irc.Message{
[125]350 Command: irc.ERR_SASLFAIL,
[112]351 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
352 }}
353 }
354 } else if msg.Params[0] == "*" {
355 dc.saslServer = nil
356 return ircError{&irc.Message{
[125]357 Command: irc.ERR_SASLABORTED,
[112]358 Params: []string{"*", "SASL authentication aborted"},
359 }}
360 } else if msg.Params[0] == "+" {
361 resp = nil
362 } else {
363 // TODO: multi-line messages
364 var err error
365 resp, err = base64.StdEncoding.DecodeString(msg.Params[0])
366 if err != nil {
367 dc.saslServer = nil
368 return ircError{&irc.Message{
[125]369 Command: irc.ERR_SASLFAIL,
[112]370 Params: []string{"*", "Invalid base64-encoded response"},
371 }}
372 }
373 }
374
375 challenge, done, err := dc.saslServer.Next(resp)
376 if err != nil {
377 dc.saslServer = nil
378 if ircErr, ok := err.(ircError); ok && ircErr.Message.Command == irc.ERR_PASSWDMISMATCH {
379 return ircError{&irc.Message{
[125]380 Command: irc.ERR_SASLFAIL,
[112]381 Params: []string{"*", ircErr.Message.Params[1]},
382 }}
383 }
384 dc.SendMessage(&irc.Message{
385 Prefix: dc.srv.prefix(),
[125]386 Command: irc.ERR_SASLFAIL,
[112]387 Params: []string{"*", "SASL error"},
388 })
389 return fmt.Errorf("SASL authentication failed: %v", err)
390 } else if done {
391 dc.saslServer = nil
392 dc.SendMessage(&irc.Message{
393 Prefix: dc.srv.prefix(),
[125]394 Command: irc.RPL_LOGGEDIN,
[112]395 Params: []string{dc.nick, dc.nick, dc.user.Username, "You are now logged in"},
396 })
397 dc.SendMessage(&irc.Message{
398 Prefix: dc.srv.prefix(),
[125]399 Command: irc.RPL_SASLSUCCESS,
[112]400 Params: []string{dc.nick, "SASL authentication successful"},
401 })
402 } else {
403 challengeStr := "+"
[135]404 if len(challenge) > 0 {
[112]405 challengeStr = base64.StdEncoding.EncodeToString(challenge)
406 }
407
408 // TODO: multi-line messages
409 dc.SendMessage(&irc.Message{
410 Prefix: dc.srv.prefix(),
411 Command: "AUTHENTICATE",
412 Params: []string{challengeStr},
413 })
414 }
[13]415 default:
[55]416 dc.logger.Printf("unhandled message: %v", msg)
[13]417 return newUnknownCommandError(msg.Command)
418 }
[108]419 if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
[55]420 return dc.register()
[13]421 }
422 return nil
423}
424
[108]425func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
[111]426 cmd = strings.ToUpper(cmd)
427
[108]428 replyTo := dc.nick
429 if !dc.registered {
430 replyTo = "*"
431 }
432
433 switch cmd {
434 case "LS":
435 if len(args) > 0 {
436 var err error
437 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
438 return err
439 }
440 }
441
[256]442 caps := []string{"message-tags", "server-time", "echo-message", "batch"}
[194]443
[112]444 if dc.capVersion >= 302 {
[108]445 caps = append(caps, "sasl=PLAIN")
446 } else {
447 caps = append(caps, "sasl")
[112]448 }
[108]449
450 // TODO: multi-line replies
451 dc.SendMessage(&irc.Message{
452 Prefix: dc.srv.prefix(),
453 Command: "CAP",
454 Params: []string{replyTo, "LS", strings.Join(caps, " ")},
455 })
456
457 if !dc.registered {
458 dc.negociatingCaps = true
459 }
460 case "LIST":
461 var caps []string
462 for name := range dc.caps {
463 caps = append(caps, name)
464 }
465
466 // TODO: multi-line replies
467 dc.SendMessage(&irc.Message{
468 Prefix: dc.srv.prefix(),
469 Command: "CAP",
470 Params: []string{replyTo, "LIST", strings.Join(caps, " ")},
471 })
472 case "REQ":
473 if len(args) == 0 {
474 return ircError{&irc.Message{
475 Command: err_invalidcapcmd,
476 Params: []string{replyTo, cmd, "Missing argument in CAP REQ command"},
477 }}
478 }
479
480 caps := strings.Fields(args[0])
481 ack := true
482 for _, name := range caps {
483 name = strings.ToLower(name)
484 enable := !strings.HasPrefix(name, "-")
485 if !enable {
486 name = strings.TrimPrefix(name, "-")
487 }
488
489 enabled := dc.caps[name]
490 if enable == enabled {
491 continue
492 }
493
494 switch name {
[256]495 case "sasl", "message-tags", "server-time", "echo-message", "batch":
[112]496 dc.caps[name] = enable
[108]497 default:
498 ack = false
499 }
500 }
501
502 reply := "NAK"
503 if ack {
504 reply = "ACK"
505 }
506 dc.SendMessage(&irc.Message{
507 Prefix: dc.srv.prefix(),
508 Command: "CAP",
509 Params: []string{replyTo, reply, args[0]},
510 })
511 case "END":
512 dc.negociatingCaps = false
513 default:
514 return ircError{&irc.Message{
515 Command: err_invalidcapcmd,
516 Params: []string{replyTo, cmd, "Unknown CAP command"},
517 }}
518 }
519 return nil
520}
521
[91]522func sanityCheckServer(addr string) error {
523 dialer := net.Dialer{Timeout: 30 * time.Second}
524 conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
525 if err != nil {
526 return err
527 }
528 return conn.Close()
529}
530
[183]531func unmarshalUsername(rawUsername string) (username, client, network string) {
[112]532 username = rawUsername
[183]533
534 i := strings.IndexAny(username, "/@")
535 j := strings.LastIndexAny(username, "/@")
536 if i >= 0 {
537 username = rawUsername[:i]
[73]538 }
[183]539 if j >= 0 {
[190]540 if rawUsername[j] == '@' {
541 client = rawUsername[j+1:]
542 } else {
543 network = rawUsername[j+1:]
544 }
[73]545 }
[183]546 if i >= 0 && j >= 0 && i < j {
[190]547 if rawUsername[i] == '@' {
548 client = rawUsername[i+1 : j]
549 } else {
550 network = rawUsername[i+1 : j]
551 }
[183]552 }
553
554 return username, client, network
[112]555}
[73]556
[168]557func (dc *downstreamConn) authenticate(username, password string) error {
[183]558 username, clientName, networkName := unmarshalUsername(username)
[168]559
[173]560 u, err := dc.srv.db.GetUser(username)
561 if err != nil {
562 dc.logger.Printf("failed authentication for %q: %v", username, err)
[168]563 return errAuthFailed
564 }
565
[173]566 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
[168]567 if err != nil {
568 dc.logger.Printf("failed authentication for %q: %v", username, err)
569 return errAuthFailed
570 }
571
[173]572 dc.user = dc.srv.getUser(username)
573 if dc.user == nil {
574 dc.logger.Printf("failed authentication for %q: user not active", username)
575 return errAuthFailed
576 }
[183]577 dc.clientName = clientName
[168]578 dc.networkName = networkName
579 return nil
580}
581
582func (dc *downstreamConn) register() error {
583 if dc.registered {
584 return fmt.Errorf("tried to register twice")
585 }
586
587 password := dc.password
588 dc.password = ""
589 if dc.user == nil {
590 if err := dc.authenticate(dc.rawUsername, password); err != nil {
591 return err
592 }
593 }
594
[183]595 if dc.clientName == "" && dc.networkName == "" {
596 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
[168]597 }
598
599 dc.registered = true
[184]600 dc.logger.Printf("registration complete for user %q", dc.user.Username)
[168]601 return nil
602}
603
604func (dc *downstreamConn) loadNetwork() error {
605 if dc.networkName == "" {
[112]606 return nil
607 }
[85]608
[168]609 network := dc.user.getNetwork(dc.networkName)
[112]610 if network == nil {
[168]611 addr := dc.networkName
[112]612 if !strings.ContainsRune(addr, ':') {
613 addr = addr + ":6697"
614 }
615
616 dc.logger.Printf("trying to connect to new network %q", addr)
617 if err := sanityCheckServer(addr); err != nil {
618 dc.logger.Printf("failed to connect to %q: %v", addr, err)
619 return ircError{&irc.Message{
620 Command: irc.ERR_PASSWDMISMATCH,
[168]621 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
[112]622 }}
623 }
624
[168]625 dc.logger.Printf("auto-saving network %q", dc.networkName)
[112]626 var err error
[120]627 network, err = dc.user.createNetwork(&Network{
[168]628 Addr: dc.networkName,
[120]629 Nick: dc.nick,
630 })
[112]631 if err != nil {
632 return err
633 }
634 }
635
636 dc.network = network
637 return nil
638}
639
[168]640func (dc *downstreamConn) welcome() error {
641 if dc.user == nil || !dc.registered {
642 panic("tried to welcome an unregistered connection")
[37]643 }
644
[168]645 // TODO: doing this might take some time. We should do it in dc.register
646 // instead, but we'll potentially be adding a new network and this must be
647 // done in the user goroutine.
648 if err := dc.loadNetwork(); err != nil {
649 return err
[85]650 }
651
[55]652 dc.SendMessage(&irc.Message{
653 Prefix: dc.srv.prefix(),
[13]654 Command: irc.RPL_WELCOME,
[98]655 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
[54]656 })
[55]657 dc.SendMessage(&irc.Message{
658 Prefix: dc.srv.prefix(),
[13]659 Command: irc.RPL_YOURHOST,
[55]660 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
[54]661 })
[55]662 dc.SendMessage(&irc.Message{
663 Prefix: dc.srv.prefix(),
[13]664 Command: irc.RPL_CREATED,
[55]665 Params: []string{dc.nick, "Who cares when the server was created?"},
[54]666 })
[55]667 dc.SendMessage(&irc.Message{
668 Prefix: dc.srv.prefix(),
[13]669 Command: irc.RPL_MYINFO,
[98]670 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
[54]671 })
[93]672 // TODO: RPL_ISUPPORT
[55]673 dc.SendMessage(&irc.Message{
674 Prefix: dc.srv.prefix(),
[13]675 Command: irc.ERR_NOMOTD,
[55]676 Params: []string{dc.nick, "No MOTD"},
[54]677 })
[13]678
[73]679 dc.forEachUpstream(func(uc *upstreamConn) {
[30]680 for _, ch := range uc.channels {
681 if ch.complete {
[132]682 dc.SendMessage(&irc.Message{
683 Prefix: dc.prefix(),
684 Command: "JOIN",
[260]685 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
[132]686 })
687
[55]688 forwardChannel(dc, ch)
[30]689 }
690 }
[143]691 })
[50]692
[143]693 dc.forEachNetwork(func(net *network) {
[253]694 // Only send history if we're the first connected client with that name
695 // for the network
696 if _, ok := net.offlineClients[dc.clientName]; ok {
697 dc.sendNetworkHistory(net)
698 delete(net.offlineClients, dc.clientName)
[227]699 }
[253]700 })
[57]701
[253]702 return nil
703}
[144]704
[253]705func (dc *downstreamConn) sendNetworkHistory(net *network) {
706 for target, history := range net.history {
707 seq, ok := history.offlineClients[dc.clientName]
708 if !ok {
709 continue
710 }
711 delete(history.offlineClients, dc.clientName)
712
713 // If all clients have received history, no need to keep the
714 // ring buffer around
715 if len(history.offlineClients) == 0 {
716 delete(net.history, target)
717 }
718
719 consumer := history.ring.NewConsumer(seq)
720
[256]721 batchRef := "history"
722 if dc.caps["batch"] {
723 dc.SendMessage(&irc.Message{
724 Prefix: dc.srv.prefix(),
725 Command: "BATCH",
[260]726 Params: []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
[256]727 })
728 }
729
[227]730 for {
[233]731 msg := consumer.Consume()
[227]732 if msg == nil {
733 break
[204]734 }
735
[245]736 // Don't replay all messages, because that would mess up client
737 // state. For instance we just sent the list of users, sending
738 // PART messages for one of these users would be incorrect.
739 ignore := true
740 switch msg.Command {
741 case "PRIVMSG", "NOTICE":
742 ignore = false
743 }
744 if ignore {
745 continue
746 }
747
[256]748 if dc.caps["batch"] {
749 msg = msg.Copy()
750 msg.Tags["batch"] = irc.TagValue(batchRef)
751 }
752
[261]753 dc.SendMessage(dc.marshalMessage(msg, net))
[227]754 }
[256]755
756 if dc.caps["batch"] {
757 dc.SendMessage(&irc.Message{
758 Prefix: dc.srv.prefix(),
759 Command: "BATCH",
760 Params: []string{"-" + batchRef},
761 })
762 }
[253]763 }
[13]764}
765
[103]766func (dc *downstreamConn) runUntilRegistered() error {
767 for !dc.registered {
[212]768 msg, err := dc.ReadMessage()
[106]769 if err != nil {
[103]770 return fmt.Errorf("failed to read IRC command: %v", err)
771 }
772
773 err = dc.handleMessage(msg)
774 if ircErr, ok := err.(ircError); ok {
775 ircErr.Message.Prefix = dc.srv.prefix()
776 dc.SendMessage(ircErr.Message)
777 } else if err != nil {
778 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
779 }
780 }
781
782 return nil
783}
784
[55]785func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
[13]786 switch msg.Command {
[111]787 case "CAP":
788 var subCmd string
789 if err := parseMessageParams(msg, &subCmd); err != nil {
790 return err
791 }
792 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
793 return err
794 }
[107]795 case "PING":
796 dc.SendMessage(&irc.Message{
797 Prefix: dc.srv.prefix(),
798 Command: "PONG",
799 Params: msg.Params,
800 })
801 return nil
[42]802 case "USER":
[13]803 return ircError{&irc.Message{
804 Command: irc.ERR_ALREADYREGISTERED,
[55]805 Params: []string{dc.nick, "You may not reregister"},
[13]806 }}
[42]807 case "NICK":
[90]808 var nick string
809 if err := parseMessageParams(msg, &nick); err != nil {
810 return err
811 }
812
813 var err error
814 dc.forEachNetwork(func(n *network) {
815 if err != nil {
816 return
817 }
818 n.Nick = nick
819 err = dc.srv.db.StoreNetwork(dc.user.Username, &n.Network)
820 })
821 if err != nil {
822 return err
823 }
824
[73]825 dc.forEachUpstream(func(uc *upstreamConn) {
[60]826 uc.SendMessage(msg)
[42]827 })
[146]828 case "JOIN":
829 var namesStr string
830 if err := parseMessageParams(msg, &namesStr); err != nil {
[48]831 return err
832 }
833
[146]834 var keys []string
835 if len(msg.Params) > 1 {
836 keys = strings.Split(msg.Params[1], ",")
837 }
838
839 for i, name := range strings.Split(namesStr, ",") {
[145]840 uc, upstreamName, err := dc.unmarshalEntity(name)
841 if err != nil {
[158]842 return err
[145]843 }
[48]844
[146]845 var key string
846 if len(keys) > i {
847 key = keys[i]
848 }
849
850 params := []string{upstreamName}
851 if key != "" {
852 params = append(params, key)
853 }
[145]854 uc.SendMessage(&irc.Message{
[146]855 Command: "JOIN",
856 Params: params,
[145]857 })
[89]858
[222]859 ch := &Channel{Name: upstreamName, Key: key}
860 if err := uc.network.createUpdateChannel(ch); err != nil {
861 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
[89]862 }
863 }
[146]864 case "PART":
865 var namesStr string
866 if err := parseMessageParams(msg, &namesStr); err != nil {
867 return err
868 }
869
870 var reason string
871 if len(msg.Params) > 1 {
872 reason = msg.Params[1]
873 }
874
875 for _, name := range strings.Split(namesStr, ",") {
876 uc, upstreamName, err := dc.unmarshalEntity(name)
877 if err != nil {
[158]878 return err
[146]879 }
880
881 params := []string{upstreamName}
882 if reason != "" {
883 params = append(params, reason)
884 }
885 uc.SendMessage(&irc.Message{
886 Command: "PART",
887 Params: params,
888 })
889
[222]890 if err := uc.network.deleteChannel(upstreamName); err != nil {
891 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
[146]892 }
893 }
[159]894 case "KICK":
895 var channelStr, userStr string
896 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
897 return err
898 }
899
900 channels := strings.Split(channelStr, ",")
901 users := strings.Split(userStr, ",")
902
903 var reason string
904 if len(msg.Params) > 2 {
905 reason = msg.Params[2]
906 }
907
908 if len(channels) != 1 && len(channels) != len(users) {
909 return ircError{&irc.Message{
910 Command: irc.ERR_BADCHANMASK,
911 Params: []string{dc.nick, channelStr, "Bad channel mask"},
912 }}
913 }
914
915 for i, user := range users {
916 var channel string
917 if len(channels) == 1 {
918 channel = channels[0]
919 } else {
920 channel = channels[i]
921 }
922
923 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
924 if err != nil {
925 return err
926 }
927
928 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
929 if err != nil {
930 return err
931 }
932
933 if ucChannel != ucUser {
934 return ircError{&irc.Message{
935 Command: irc.ERR_USERNOTINCHANNEL,
936 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
937 }}
938 }
939 uc := ucChannel
940
941 params := []string{upstreamChannel, upstreamUser}
942 if reason != "" {
943 params = append(params, reason)
944 }
945 uc.SendMessage(&irc.Message{
946 Command: "KICK",
947 Params: params,
948 })
949 }
[69]950 case "MODE":
[46]951 var name string
952 if err := parseMessageParams(msg, &name); err != nil {
953 return err
954 }
955
956 var modeStr string
957 if len(msg.Params) > 1 {
958 modeStr = msg.Params[1]
959 }
960
[139]961 if name == dc.nick {
[46]962 if modeStr != "" {
[73]963 dc.forEachUpstream(func(uc *upstreamConn) {
[69]964 uc.SendMessage(&irc.Message{
965 Command: "MODE",
966 Params: []string{uc.nick, modeStr},
967 })
[46]968 })
969 } else {
[55]970 dc.SendMessage(&irc.Message{
971 Prefix: dc.srv.prefix(),
[46]972 Command: irc.RPL_UMODEIS,
[129]973 Params: []string{dc.nick, ""}, // TODO
[54]974 })
[46]975 }
[139]976 return nil
[46]977 }
[139]978
979 uc, upstreamName, err := dc.unmarshalEntity(name)
980 if err != nil {
981 return err
982 }
983
984 if !uc.isChannel(upstreamName) {
985 return ircError{&irc.Message{
986 Command: irc.ERR_USERSDONTMATCH,
987 Params: []string{dc.nick, "Cannot change mode for other users"},
988 }}
989 }
990
991 if modeStr != "" {
992 params := []string{upstreamName, modeStr}
993 params = append(params, msg.Params[2:]...)
994 uc.SendMessage(&irc.Message{
995 Command: "MODE",
996 Params: params,
997 })
998 } else {
999 ch, ok := uc.channels[upstreamName]
1000 if !ok {
1001 return ircError{&irc.Message{
1002 Command: irc.ERR_NOSUCHCHANNEL,
1003 Params: []string{dc.nick, name, "No such channel"},
1004 }}
1005 }
1006
1007 if ch.modes == nil {
1008 // we haven't received the initial RPL_CHANNELMODEIS yet
1009 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1010 return nil
1011 }
1012
1013 modeStr, modeParams := ch.modes.Format()
1014 params := []string{dc.nick, name, modeStr}
1015 params = append(params, modeParams...)
1016
1017 dc.SendMessage(&irc.Message{
1018 Prefix: dc.srv.prefix(),
1019 Command: irc.RPL_CHANNELMODEIS,
1020 Params: params,
1021 })
[162]1022 if ch.creationTime != "" {
1023 dc.SendMessage(&irc.Message{
1024 Prefix: dc.srv.prefix(),
1025 Command: rpl_creationtime,
1026 Params: []string{dc.nick, name, ch.creationTime},
1027 })
1028 }
[139]1029 }
[160]1030 case "TOPIC":
1031 var channel string
1032 if err := parseMessageParams(msg, &channel); err != nil {
1033 return err
1034 }
1035
1036 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1037 if err != nil {
1038 return err
1039 }
1040
1041 if len(msg.Params) > 1 { // setting topic
1042 topic := msg.Params[1]
1043 uc.SendMessage(&irc.Message{
1044 Command: "TOPIC",
1045 Params: []string{upstreamChannel, topic},
1046 })
1047 } else { // getting topic
1048 ch, ok := uc.channels[upstreamChannel]
1049 if !ok {
1050 return ircError{&irc.Message{
1051 Command: irc.ERR_NOSUCHCHANNEL,
1052 Params: []string{dc.nick, upstreamChannel, "No such channel"},
1053 }}
1054 }
1055 sendTopic(dc, ch)
1056 }
[177]1057 case "LIST":
1058 // TODO: support ELIST when supported by all upstreams
1059
1060 pl := pendingLIST{
1061 downstreamID: dc.id,
1062 pendingCommands: make(map[int64]*irc.Message),
1063 }
1064 var upstreamChannels map[int64][]string
1065 if len(msg.Params) > 0 {
1066 upstreamChannels = make(map[int64][]string)
1067 channels := strings.Split(msg.Params[0], ",")
1068 for _, channel := range channels {
1069 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1070 if err != nil {
1071 return err
1072 }
1073 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1074 }
1075 }
1076
1077 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1078 dc.forEachUpstream(func(uc *upstreamConn) {
1079 var params []string
1080 if upstreamChannels != nil {
1081 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1082 params = []string{strings.Join(channels, ",")}
1083 } else {
1084 return
1085 }
1086 }
1087 pl.pendingCommands[uc.network.ID] = &irc.Message{
1088 Command: "LIST",
1089 Params: params,
1090 }
[181]1091 uc.trySendLIST(dc.id)
[177]1092 })
[140]1093 case "NAMES":
1094 if len(msg.Params) == 0 {
1095 dc.SendMessage(&irc.Message{
1096 Prefix: dc.srv.prefix(),
1097 Command: irc.RPL_ENDOFNAMES,
1098 Params: []string{dc.nick, "*", "End of /NAMES list"},
1099 })
1100 return nil
1101 }
1102
1103 channels := strings.Split(msg.Params[0], ",")
1104 for _, channel := range channels {
1105 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1106 if err != nil {
1107 return err
1108 }
1109
1110 ch, ok := uc.channels[upstreamChannel]
1111 if ok {
1112 sendNames(dc, ch)
1113 } else {
1114 // NAMES on a channel we have not joined, ask upstream
[176]1115 uc.SendMessageLabeled(dc.id, &irc.Message{
[140]1116 Command: "NAMES",
1117 Params: []string{upstreamChannel},
1118 })
1119 }
1120 }
[127]1121 case "WHO":
1122 if len(msg.Params) == 0 {
1123 // TODO: support WHO without parameters
1124 dc.SendMessage(&irc.Message{
1125 Prefix: dc.srv.prefix(),
1126 Command: irc.RPL_ENDOFWHO,
[140]1127 Params: []string{dc.nick, "*", "End of /WHO list"},
[127]1128 })
1129 return nil
1130 }
1131
1132 // TODO: support WHO masks
1133 entity := msg.Params[0]
1134
[142]1135 if entity == dc.nick {
1136 // TODO: support AWAY (H/G) in self WHO reply
1137 dc.SendMessage(&irc.Message{
1138 Prefix: dc.srv.prefix(),
1139 Command: irc.RPL_WHOREPLY,
[184]1140 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
[142]1141 })
1142 dc.SendMessage(&irc.Message{
1143 Prefix: dc.srv.prefix(),
1144 Command: irc.RPL_ENDOFWHO,
1145 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1146 })
1147 return nil
1148 }
1149
[127]1150 uc, upstreamName, err := dc.unmarshalEntity(entity)
1151 if err != nil {
1152 return err
1153 }
1154
1155 var params []string
1156 if len(msg.Params) == 2 {
1157 params = []string{upstreamName, msg.Params[1]}
1158 } else {
1159 params = []string{upstreamName}
1160 }
1161
[176]1162 uc.SendMessageLabeled(dc.id, &irc.Message{
[127]1163 Command: "WHO",
1164 Params: params,
1165 })
[128]1166 case "WHOIS":
1167 if len(msg.Params) == 0 {
1168 return ircError{&irc.Message{
1169 Command: irc.ERR_NONICKNAMEGIVEN,
1170 Params: []string{dc.nick, "No nickname given"},
1171 }}
1172 }
1173
1174 var target, mask string
1175 if len(msg.Params) == 1 {
1176 target = ""
1177 mask = msg.Params[0]
1178 } else {
1179 target = msg.Params[0]
1180 mask = msg.Params[1]
1181 }
1182 // TODO: support multiple WHOIS users
1183 if i := strings.IndexByte(mask, ','); i >= 0 {
1184 mask = mask[:i]
1185 }
1186
[142]1187 if mask == dc.nick {
1188 dc.SendMessage(&irc.Message{
1189 Prefix: dc.srv.prefix(),
1190 Command: irc.RPL_WHOISUSER,
[184]1191 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
[142]1192 })
1193 dc.SendMessage(&irc.Message{
1194 Prefix: dc.srv.prefix(),
1195 Command: irc.RPL_WHOISSERVER,
1196 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1197 })
1198 dc.SendMessage(&irc.Message{
1199 Prefix: dc.srv.prefix(),
1200 Command: irc.RPL_ENDOFWHOIS,
1201 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1202 })
1203 return nil
1204 }
1205
[128]1206 // TODO: support WHOIS masks
1207 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1208 if err != nil {
1209 return err
1210 }
1211
1212 var params []string
1213 if target != "" {
1214 params = []string{target, upstreamNick}
1215 } else {
1216 params = []string{upstreamNick}
1217 }
1218
[176]1219 uc.SendMessageLabeled(dc.id, &irc.Message{
[128]1220 Command: "WHOIS",
1221 Params: params,
1222 })
[58]1223 case "PRIVMSG":
1224 var targetsStr, text string
1225 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1226 return err
1227 }
1228
1229 for _, name := range strings.Split(targetsStr, ",") {
[117]1230 if name == serviceNick {
1231 handleServicePRIVMSG(dc, text)
1232 continue
1233 }
1234
[127]1235 uc, upstreamName, err := dc.unmarshalEntity(name)
[58]1236 if err != nil {
1237 return err
1238 }
1239
[95]1240 if upstreamName == "NickServ" {
1241 dc.handleNickServPRIVMSG(uc, text)
1242 }
1243
[268]1244 unmarshaledText := text
1245 if uc.isChannel(upstreamName) {
1246 unmarshaledText = dc.unmarshalText(uc, text)
1247 }
[69]1248 uc.SendMessage(&irc.Message{
[58]1249 Command: "PRIVMSG",
[268]1250 Params: []string{upstreamName, unmarshaledText},
[60]1251 })
[105]1252
[113]1253 echoMsg := &irc.Message{
1254 Prefix: &irc.Prefix{
1255 Name: uc.nick,
1256 User: uc.username,
1257 },
[114]1258 Command: "PRIVMSG",
[113]1259 Params: []string{upstreamName, text},
1260 }
[105]1261
[239]1262 uc.produce(upstreamName, echoMsg, dc)
[58]1263 }
[164]1264 case "NOTICE":
1265 var targetsStr, text string
1266 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1267 return err
1268 }
1269
1270 for _, name := range strings.Split(targetsStr, ",") {
1271 uc, upstreamName, err := dc.unmarshalEntity(name)
1272 if err != nil {
1273 return err
1274 }
1275
[268]1276 unmarshaledText := text
1277 if uc.isChannel(upstreamName) {
1278 unmarshaledText = dc.unmarshalText(uc, text)
1279 }
[164]1280 uc.SendMessage(&irc.Message{
1281 Command: "NOTICE",
[268]1282 Params: []string{upstreamName, unmarshaledText},
[164]1283 })
1284 }
[163]1285 case "INVITE":
1286 var user, channel string
1287 if err := parseMessageParams(msg, &user, &channel); err != nil {
1288 return err
1289 }
1290
1291 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1292 if err != nil {
1293 return err
1294 }
1295
1296 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1297 if err != nil {
1298 return err
1299 }
1300
1301 if ucChannel != ucUser {
1302 return ircError{&irc.Message{
1303 Command: irc.ERR_USERNOTINCHANNEL,
1304 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1305 }}
1306 }
1307 uc := ucChannel
1308
[176]1309 uc.SendMessageLabeled(dc.id, &irc.Message{
[163]1310 Command: "INVITE",
1311 Params: []string{upstreamUser, upstreamChannel},
1312 })
[13]1313 default:
[55]1314 dc.logger.Printf("unhandled message: %v", msg)
[13]1315 return newUnknownCommandError(msg.Command)
1316 }
[42]1317 return nil
[13]1318}
[95]1319
1320func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1321 username, password, ok := parseNickServCredentials(text, uc.nick)
1322 if !ok {
1323 return
1324 }
1325
1326 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1327 n := uc.network
1328 n.SASL.Mechanism = "PLAIN"
1329 n.SASL.Plain.Username = username
1330 n.SASL.Plain.Password = password
1331 if err := dc.srv.db.StoreNetwork(dc.user.Username, &n.Network); err != nil {
1332 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1333 }
1334}
1335
1336func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1337 fields := strings.Fields(text)
1338 if len(fields) < 2 {
1339 return "", "", false
1340 }
1341 cmd := strings.ToUpper(fields[0])
1342 params := fields[1:]
1343 switch cmd {
1344 case "REGISTER":
1345 username = nick
1346 password = params[0]
1347 case "IDENTIFY":
1348 if len(params) == 1 {
1349 username = nick
[182]1350 password = params[0]
[95]1351 } else {
1352 username = params[0]
[182]1353 password = params[1]
[95]1354 }
[182]1355 case "SET":
1356 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1357 username = nick
1358 password = params[1]
1359 }
[95]1360 }
1361 return username, password, true
1362}
Note: See TracBrowser for help on using the repository browser.