source: code/trunk/downstream.go@ 478

Last change on this file since 478 was 478, checked in by hubert, 4 years ago

Implement casemapping

TL;DR: supports for casemapping, now logs are saved in
casemapped/canonical/tolower form
(eg. in the #channel directory instead of #Channel... or something)

What is casemapping?

see <https://modern.ircdocs.horse/#casemapping-parameter>

Casemapping and multi-upstream

Since each upstream does not necessarily use the same casemapping, and
since casemappings cannot coexist [0],

  1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent,
  2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii).

[0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same
user (upstreams that advertise rfc1459 for example), while on others
(upstreams that advertise ascii) they don't.

Once upstream's casemapping is known (default to rfc1459), entity names
in map keys are made into casemapped form, for upstreamConn,
upstreamChannel and network.

downstreamConn advertises "CASEMAPPING=ascii", and always casemap map
keys with ascii.

Some functions require the caller to casemap their argument (to avoid
needless calls to casemapping functions).

Message forwarding and casemapping

downstream message handling (joins and parts basically):
When relaying entity names from downstreams to upstreams, soju uses the
upstream casemapping, in order to not get in the way of the user. This
does not brings any issue, as long as soju replies with the ascii
casemapping in mind (solves point 1.).

marshalEntity/marshalUserPrefix:
When relaying entity names from upstreams with non-ascii casemappings,
soju *partially* casemap them: it only change the case of characters
which are not ascii letters. ASCII case is thus kept intact, while
special symbols like []{} are the same every time soju sends them to
downstreams (solves point 2.).

Casemapping changes

Casemapping changes are not fully supported by this patch and will
result in loss of history. This is a limitation of the protocol and
should be solved by the RENAME spec.

File size: 47.3 KB
Line 
1package soju
2
3import (
4 "crypto/tls"
5 "encoding/base64"
6 "fmt"
7 "io"
8 "net"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/emersion/go-sasl"
14 "golang.org/x/crypto/bcrypt"
15 "gopkg.in/irc.v3"
16)
17
18type ircError struct {
19 Message *irc.Message
20}
21
22func (err ircError) Error() string {
23 return err.Message.String()
24}
25
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
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
55var errAuthFailed = ircError{&irc.Message{
56 Command: irc.ERR_PASSWDMISMATCH,
57 Params: []string{"*", "Invalid username or password"},
58}}
59
60// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
61// '*' and '?' break masks
62const illegalNickChars = " :@!*?"
63
64// permanentDownstreamCaps is the list of always-supported downstream
65// capabilities.
66var permanentDownstreamCaps = map[string]string{
67 "batch": "",
68 "cap-notify": "",
69 "echo-message": "",
70 "invite-notify": "",
71 "message-tags": "",
72 "sasl": "PLAIN",
73 "server-time": "",
74}
75
76// needAllDownstreamCaps is the list of downstream capabilities that
77// require support from all upstreams to be enabled
78var needAllDownstreamCaps = map[string]string{
79 "away-notify": "",
80 "extended-join": "",
81 "multi-prefix": "",
82}
83
84// passthroughIsupport is the set of ISUPPORT tokens that are directly passed
85// through from the upstream server to downstream clients.
86//
87// This is only effective in single-upstream mode.
88var passthroughIsupport = map[string]bool{
89 "AWAYLEN": true,
90 "CHANLIMIT": true,
91 "CHANMODES": true,
92 "CHANNELLEN": true,
93 "CHANTYPES": true,
94 "EXCEPTS": true,
95 "EXTBAN": true,
96 "HOSTLEN": true,
97 "INVEX": true,
98 "KICKLEN": true,
99 "MAXLIST": true,
100 "MAXTARGETS": true,
101 "MODES": true,
102 "NETWORK": true,
103 "NICKLEN": true,
104 "PREFIX": true,
105 "SAFELIST": true,
106 "TARGMAX": true,
107 "TOPICLEN": true,
108 "USERLEN": true,
109}
110
111type downstreamConn struct {
112 conn
113
114 id uint64
115
116 registered bool
117 user *user
118 nick string
119 nickCM string
120 rawUsername string
121 networkName string
122 clientName string
123 realname string
124 hostname string
125 password string // empty after authentication
126 network *network // can be nil
127
128 negociatingCaps bool
129 capVersion int
130 supportedCaps map[string]string
131 caps map[string]bool
132
133 saslServer sasl.Server
134}
135
136func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
137 remoteAddr := ic.RemoteAddr().String()
138 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
139 options := connOptions{Logger: logger}
140 dc := &downstreamConn{
141 conn: *newConn(srv, ic, &options),
142 id: id,
143 supportedCaps: make(map[string]string),
144 caps: make(map[string]bool),
145 }
146 dc.hostname = remoteAddr
147 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
148 dc.hostname = host
149 }
150 for k, v := range permanentDownstreamCaps {
151 dc.supportedCaps[k] = v
152 }
153 if srv.LogPath != "" {
154 dc.supportedCaps["draft/chathistory"] = ""
155 }
156 return dc
157}
158
159func (dc *downstreamConn) prefix() *irc.Prefix {
160 return &irc.Prefix{
161 Name: dc.nick,
162 User: dc.user.Username,
163 Host: dc.hostname,
164 }
165}
166
167func (dc *downstreamConn) forEachNetwork(f func(*network)) {
168 if dc.network != nil {
169 f(dc.network)
170 } else {
171 dc.user.forEachNetwork(f)
172 }
173}
174
175func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
176 dc.user.forEachUpstream(func(uc *upstreamConn) {
177 if dc.network != nil && uc.network != dc.network {
178 return
179 }
180 f(uc)
181 })
182}
183
184// upstream returns the upstream connection, if any. If there are zero or if
185// there are multiple upstream connections, it returns nil.
186func (dc *downstreamConn) upstream() *upstreamConn {
187 if dc.network == nil {
188 return nil
189 }
190 return dc.network.conn
191}
192
193func isOurNick(net *network, nick string) bool {
194 // TODO: this doesn't account for nick changes
195 if net.conn != nil {
196 return net.casemap(nick) == net.conn.nickCM
197 }
198 // We're not currently connected to the upstream connection, so we don't
199 // know whether this name is our nickname. Best-effort: use the network's
200 // configured nickname and hope it was the one being used when we were
201 // connected.
202 return net.casemap(nick) == net.casemap(net.Nick)
203}
204
205// marshalEntity converts an upstream entity name (ie. channel or nick) into a
206// downstream entity name.
207//
208// This involves adding a "/<network>" suffix if the entity isn't the current
209// user.
210func (dc *downstreamConn) marshalEntity(net *network, name string) string {
211 if isOurNick(net, name) {
212 return dc.nick
213 }
214 name = partialCasemap(net.casemap, name)
215 if dc.network != nil {
216 if dc.network != net {
217 panic("soju: tried to marshal an entity for another network")
218 }
219 return name
220 }
221 return name + "/" + net.GetName()
222}
223
224func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
225 if isOurNick(net, prefix.Name) {
226 return dc.prefix()
227 }
228 prefix.Name = partialCasemap(net.casemap, prefix.Name)
229 if dc.network != nil {
230 if dc.network != net {
231 panic("soju: tried to marshal a user prefix for another network")
232 }
233 return prefix
234 }
235 return &irc.Prefix{
236 Name: prefix.Name + "/" + net.GetName(),
237 User: prefix.User,
238 Host: prefix.Host,
239 }
240}
241
242// unmarshalEntity converts a downstream entity name (ie. channel or nick) into
243// an upstream entity name.
244//
245// This involves removing the "/<network>" suffix.
246func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
247 if uc := dc.upstream(); uc != nil {
248 return uc, name, nil
249 }
250 if dc.network != nil {
251 return nil, "", ircError{&irc.Message{
252 Command: irc.ERR_NOSUCHCHANNEL,
253 Params: []string{name, "Disconnected from upstream network"},
254 }}
255 }
256
257 var conn *upstreamConn
258 if i := strings.LastIndexByte(name, '/'); i >= 0 {
259 network := name[i+1:]
260 name = name[:i]
261
262 dc.forEachUpstream(func(uc *upstreamConn) {
263 if network != uc.network.GetName() {
264 return
265 }
266 conn = uc
267 })
268 }
269
270 if conn == nil {
271 return nil, "", ircError{&irc.Message{
272 Command: irc.ERR_NOSUCHCHANNEL,
273 Params: []string{name, "Missing network suffix in channel name"},
274 }}
275 }
276 return conn, name, nil
277}
278
279func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
280 if dc.upstream() != nil {
281 return text
282 }
283 // TODO: smarter parsing that ignores URLs
284 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
285}
286
287func (dc *downstreamConn) readMessages(ch chan<- event) error {
288 for {
289 msg, err := dc.ReadMessage()
290 if err == io.EOF {
291 break
292 } else if err != nil {
293 return fmt.Errorf("failed to read IRC command: %v", err)
294 }
295
296 ch <- eventDownstreamMessage{msg, dc}
297 }
298
299 return nil
300}
301
302// SendMessage sends an outgoing message.
303//
304// This can only called from the user goroutine.
305func (dc *downstreamConn) SendMessage(msg *irc.Message) {
306 if !dc.caps["message-tags"] {
307 if msg.Command == "TAGMSG" {
308 return
309 }
310 msg = msg.Copy()
311 for name := range msg.Tags {
312 supported := false
313 switch name {
314 case "time":
315 supported = dc.caps["server-time"]
316 }
317 if !supported {
318 delete(msg.Tags, name)
319 }
320 }
321 }
322 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
323 msg.Params = msg.Params[:1]
324 }
325
326 dc.conn.SendMessage(msg)
327}
328
329// sendMessageWithID sends an outgoing message with the specified internal ID.
330func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
331 dc.SendMessage(msg)
332
333 if id == "" || !dc.messageSupportsHistory(msg) {
334 return
335 }
336
337 dc.sendPing(id)
338}
339
340// advanceMessageWithID advances history to the specified message ID without
341// sending a message. This is useful e.g. for self-messages when echo-message
342// isn't enabled.
343func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
344 if id == "" || !dc.messageSupportsHistory(msg) {
345 return
346 }
347
348 dc.sendPing(id)
349}
350
351// ackMsgID acknowledges that a message has been received.
352func (dc *downstreamConn) ackMsgID(id string) {
353 netID, entity, _, err := parseMsgID(id)
354 if err != nil {
355 dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
356 return
357 }
358
359 network := dc.user.getNetworkByID(netID)
360 if network == nil {
361 return
362 }
363
364 delivered := network.delivered.Value(entity)
365 if delivered == nil {
366 return
367 }
368
369 delivered[dc.clientName] = id
370}
371
372func (dc *downstreamConn) sendPing(msgID string) {
373 token := "soju-msgid-" + base64.RawURLEncoding.EncodeToString([]byte(msgID))
374 dc.SendMessage(&irc.Message{
375 Command: "PING",
376 Params: []string{token},
377 })
378}
379
380func (dc *downstreamConn) handlePong(token string) {
381 if !strings.HasPrefix(token, "soju-msgid-") {
382 dc.logger.Printf("received unrecognized PONG token %q", token)
383 return
384 }
385 token = strings.TrimPrefix(token, "soju-msgid-")
386 b, err := base64.RawURLEncoding.DecodeString(token)
387 if err != nil {
388 dc.logger.Printf("received malformed PONG token: %v", err)
389 return
390 }
391 msgID := string(b)
392
393 dc.ackMsgID(msgID)
394}
395
396// marshalMessage re-formats a message coming from an upstream connection so
397// that it's suitable for being sent on this downstream connection. Only
398// messages that may appear in logs are supported, except MODE.
399func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
400 msg = msg.Copy()
401 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
402
403 switch msg.Command {
404 case "PRIVMSG", "NOTICE", "TAGMSG":
405 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
406 case "NICK":
407 // Nick change for another user
408 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
409 case "JOIN", "PART":
410 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
411 case "KICK":
412 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
413 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
414 case "TOPIC":
415 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
416 case "QUIT":
417 // This space is intentionally left blank
418 default:
419 panic(fmt.Sprintf("unexpected %q message", msg.Command))
420 }
421
422 return msg
423}
424
425func (dc *downstreamConn) handleMessage(msg *irc.Message) error {
426 switch msg.Command {
427 case "QUIT":
428 return dc.Close()
429 default:
430 if dc.registered {
431 return dc.handleMessageRegistered(msg)
432 } else {
433 return dc.handleMessageUnregistered(msg)
434 }
435 }
436}
437
438func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
439 switch msg.Command {
440 case "NICK":
441 var nick string
442 if err := parseMessageParams(msg, &nick); err != nil {
443 return err
444 }
445 if strings.ContainsAny(nick, illegalNickChars) {
446 return ircError{&irc.Message{
447 Command: irc.ERR_ERRONEUSNICKNAME,
448 Params: []string{dc.nick, nick, "contains illegal characters"},
449 }}
450 }
451 nickCM := casemapASCII(nick)
452 if nickCM == serviceNickCM {
453 return ircError{&irc.Message{
454 Command: irc.ERR_NICKNAMEINUSE,
455 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
456 }}
457 }
458 dc.nick = nick
459 dc.nickCM = nickCM
460 case "USER":
461 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
462 return err
463 }
464 case "PASS":
465 if err := parseMessageParams(msg, &dc.password); err != nil {
466 return err
467 }
468 case "CAP":
469 var subCmd string
470 if err := parseMessageParams(msg, &subCmd); err != nil {
471 return err
472 }
473 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
474 return err
475 }
476 case "AUTHENTICATE":
477 if !dc.caps["sasl"] {
478 return ircError{&irc.Message{
479 Command: irc.ERR_SASLFAIL,
480 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
481 }}
482 }
483 if len(msg.Params) == 0 {
484 return ircError{&irc.Message{
485 Command: irc.ERR_SASLFAIL,
486 Params: []string{"*", "Missing AUTHENTICATE argument"},
487 }}
488 }
489 if dc.nick == "" {
490 return ircError{&irc.Message{
491 Command: irc.ERR_SASLFAIL,
492 Params: []string{"*", "Expected NICK command before AUTHENTICATE"},
493 }}
494 }
495
496 var resp []byte
497 if dc.saslServer == nil {
498 mech := strings.ToUpper(msg.Params[0])
499 switch mech {
500 case "PLAIN":
501 dc.saslServer = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
502 return dc.authenticate(username, password)
503 }))
504 default:
505 return ircError{&irc.Message{
506 Command: irc.ERR_SASLFAIL,
507 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
508 }}
509 }
510 } else if msg.Params[0] == "*" {
511 dc.saslServer = nil
512 return ircError{&irc.Message{
513 Command: irc.ERR_SASLABORTED,
514 Params: []string{"*", "SASL authentication aborted"},
515 }}
516 } else if msg.Params[0] == "+" {
517 resp = nil
518 } else {
519 // TODO: multi-line messages
520 var err error
521 resp, err = base64.StdEncoding.DecodeString(msg.Params[0])
522 if err != nil {
523 dc.saslServer = nil
524 return ircError{&irc.Message{
525 Command: irc.ERR_SASLFAIL,
526 Params: []string{"*", "Invalid base64-encoded response"},
527 }}
528 }
529 }
530
531 challenge, done, err := dc.saslServer.Next(resp)
532 if err != nil {
533 dc.saslServer = nil
534 if ircErr, ok := err.(ircError); ok && ircErr.Message.Command == irc.ERR_PASSWDMISMATCH {
535 return ircError{&irc.Message{
536 Command: irc.ERR_SASLFAIL,
537 Params: []string{"*", ircErr.Message.Params[1]},
538 }}
539 }
540 dc.SendMessage(&irc.Message{
541 Prefix: dc.srv.prefix(),
542 Command: irc.ERR_SASLFAIL,
543 Params: []string{"*", "SASL error"},
544 })
545 return fmt.Errorf("SASL authentication failed: %v", err)
546 } else if done {
547 dc.saslServer = nil
548 dc.SendMessage(&irc.Message{
549 Prefix: dc.srv.prefix(),
550 Command: irc.RPL_LOGGEDIN,
551 Params: []string{dc.nick, dc.prefix().String(), dc.user.Username, "You are now logged in"},
552 })
553 dc.SendMessage(&irc.Message{
554 Prefix: dc.srv.prefix(),
555 Command: irc.RPL_SASLSUCCESS,
556 Params: []string{dc.nick, "SASL authentication successful"},
557 })
558 } else {
559 challengeStr := "+"
560 if len(challenge) > 0 {
561 challengeStr = base64.StdEncoding.EncodeToString(challenge)
562 }
563
564 // TODO: multi-line messages
565 dc.SendMessage(&irc.Message{
566 Prefix: dc.srv.prefix(),
567 Command: "AUTHENTICATE",
568 Params: []string{challengeStr},
569 })
570 }
571 default:
572 dc.logger.Printf("unhandled message: %v", msg)
573 return newUnknownCommandError(msg.Command)
574 }
575 if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
576 return dc.register()
577 }
578 return nil
579}
580
581func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
582 cmd = strings.ToUpper(cmd)
583
584 replyTo := dc.nick
585 if !dc.registered {
586 replyTo = "*"
587 }
588
589 switch cmd {
590 case "LS":
591 if len(args) > 0 {
592 var err error
593 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
594 return err
595 }
596 }
597 if !dc.registered && dc.capVersion >= 302 {
598 // Let downstream show everything it supports, and trim
599 // down the available capabilities when upstreams are
600 // known.
601 for k, v := range needAllDownstreamCaps {
602 dc.supportedCaps[k] = v
603 }
604 }
605
606 caps := make([]string, 0, len(dc.supportedCaps))
607 for k, v := range dc.supportedCaps {
608 if dc.capVersion >= 302 && v != "" {
609 caps = append(caps, k+"="+v)
610 } else {
611 caps = append(caps, k)
612 }
613 }
614
615 // TODO: multi-line replies
616 dc.SendMessage(&irc.Message{
617 Prefix: dc.srv.prefix(),
618 Command: "CAP",
619 Params: []string{replyTo, "LS", strings.Join(caps, " ")},
620 })
621
622 if dc.capVersion >= 302 {
623 // CAP version 302 implicitly enables cap-notify
624 dc.caps["cap-notify"] = true
625 }
626
627 if !dc.registered {
628 dc.negociatingCaps = true
629 }
630 case "LIST":
631 var caps []string
632 for name := range dc.caps {
633 caps = append(caps, name)
634 }
635
636 // TODO: multi-line replies
637 dc.SendMessage(&irc.Message{
638 Prefix: dc.srv.prefix(),
639 Command: "CAP",
640 Params: []string{replyTo, "LIST", strings.Join(caps, " ")},
641 })
642 case "REQ":
643 if len(args) == 0 {
644 return ircError{&irc.Message{
645 Command: err_invalidcapcmd,
646 Params: []string{replyTo, cmd, "Missing argument in CAP REQ command"},
647 }}
648 }
649
650 // TODO: atomically ack/nak the whole capability set
651 caps := strings.Fields(args[0])
652 ack := true
653 for _, name := range caps {
654 name = strings.ToLower(name)
655 enable := !strings.HasPrefix(name, "-")
656 if !enable {
657 name = strings.TrimPrefix(name, "-")
658 }
659
660 if enable == dc.caps[name] {
661 continue
662 }
663
664 _, ok := dc.supportedCaps[name]
665 if !ok {
666 ack = false
667 break
668 }
669
670 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
671 // cap-notify cannot be disabled with CAP version 302
672 ack = false
673 break
674 }
675
676 dc.caps[name] = enable
677 }
678
679 reply := "NAK"
680 if ack {
681 reply = "ACK"
682 }
683 dc.SendMessage(&irc.Message{
684 Prefix: dc.srv.prefix(),
685 Command: "CAP",
686 Params: []string{replyTo, reply, args[0]},
687 })
688 case "END":
689 dc.negociatingCaps = false
690 default:
691 return ircError{&irc.Message{
692 Command: err_invalidcapcmd,
693 Params: []string{replyTo, cmd, "Unknown CAP command"},
694 }}
695 }
696 return nil
697}
698
699func (dc *downstreamConn) setSupportedCap(name, value string) {
700 prevValue, hasPrev := dc.supportedCaps[name]
701 changed := !hasPrev || prevValue != value
702 dc.supportedCaps[name] = value
703
704 if !dc.caps["cap-notify"] || !changed {
705 return
706 }
707
708 replyTo := dc.nick
709 if !dc.registered {
710 replyTo = "*"
711 }
712
713 cap := name
714 if value != "" && dc.capVersion >= 302 {
715 cap = name + "=" + value
716 }
717
718 dc.SendMessage(&irc.Message{
719 Prefix: dc.srv.prefix(),
720 Command: "CAP",
721 Params: []string{replyTo, "NEW", cap},
722 })
723}
724
725func (dc *downstreamConn) unsetSupportedCap(name string) {
726 _, hasPrev := dc.supportedCaps[name]
727 delete(dc.supportedCaps, name)
728 delete(dc.caps, name)
729
730 if !dc.caps["cap-notify"] || !hasPrev {
731 return
732 }
733
734 replyTo := dc.nick
735 if !dc.registered {
736 replyTo = "*"
737 }
738
739 dc.SendMessage(&irc.Message{
740 Prefix: dc.srv.prefix(),
741 Command: "CAP",
742 Params: []string{replyTo, "DEL", name},
743 })
744}
745
746func (dc *downstreamConn) updateSupportedCaps() {
747 supportedCaps := make(map[string]bool)
748 for cap := range needAllDownstreamCaps {
749 supportedCaps[cap] = true
750 }
751 dc.forEachUpstream(func(uc *upstreamConn) {
752 for cap, supported := range supportedCaps {
753 supportedCaps[cap] = supported && uc.caps[cap]
754 }
755 })
756
757 for cap, supported := range supportedCaps {
758 if supported {
759 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
760 } else {
761 dc.unsetSupportedCap(cap)
762 }
763 }
764}
765
766func (dc *downstreamConn) updateNick() {
767 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
768 dc.SendMessage(&irc.Message{
769 Prefix: dc.prefix(),
770 Command: "NICK",
771 Params: []string{uc.nick},
772 })
773 dc.nick = uc.nick
774 dc.nickCM = casemapASCII(dc.nick)
775 }
776}
777
778func sanityCheckServer(addr string) error {
779 dialer := net.Dialer{Timeout: 30 * time.Second}
780 conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
781 if err != nil {
782 return err
783 }
784 return conn.Close()
785}
786
787func unmarshalUsername(rawUsername string) (username, client, network string) {
788 username = rawUsername
789
790 i := strings.IndexAny(username, "/@")
791 j := strings.LastIndexAny(username, "/@")
792 if i >= 0 {
793 username = rawUsername[:i]
794 }
795 if j >= 0 {
796 if rawUsername[j] == '@' {
797 client = rawUsername[j+1:]
798 } else {
799 network = rawUsername[j+1:]
800 }
801 }
802 if i >= 0 && j >= 0 && i < j {
803 if rawUsername[i] == '@' {
804 client = rawUsername[i+1 : j]
805 } else {
806 network = rawUsername[i+1 : j]
807 }
808 }
809
810 return username, client, network
811}
812
813func (dc *downstreamConn) authenticate(username, password string) error {
814 username, clientName, networkName := unmarshalUsername(username)
815
816 u, err := dc.srv.db.GetUser(username)
817 if err != nil {
818 dc.logger.Printf("failed authentication for %q: user not found: %v", username, err)
819 return errAuthFailed
820 }
821
822 // Password auth disabled
823 if u.Password == "" {
824 return errAuthFailed
825 }
826
827 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
828 if err != nil {
829 dc.logger.Printf("failed authentication for %q: wrong password: %v", username, err)
830 return errAuthFailed
831 }
832
833 dc.user = dc.srv.getUser(username)
834 if dc.user == nil {
835 dc.logger.Printf("failed authentication for %q: user not active", username)
836 return errAuthFailed
837 }
838 dc.clientName = clientName
839 dc.networkName = networkName
840 return nil
841}
842
843func (dc *downstreamConn) register() error {
844 if dc.registered {
845 return fmt.Errorf("tried to register twice")
846 }
847
848 password := dc.password
849 dc.password = ""
850 if dc.user == nil {
851 if err := dc.authenticate(dc.rawUsername, password); err != nil {
852 return err
853 }
854 }
855
856 if dc.clientName == "" && dc.networkName == "" {
857 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
858 }
859
860 dc.registered = true
861 dc.logger.Printf("registration complete for user %q", dc.user.Username)
862 return nil
863}
864
865func (dc *downstreamConn) loadNetwork() error {
866 if dc.networkName == "" {
867 return nil
868 }
869
870 network := dc.user.getNetwork(dc.networkName)
871 if network == nil {
872 addr := dc.networkName
873 if !strings.ContainsRune(addr, ':') {
874 addr = addr + ":6697"
875 }
876
877 dc.logger.Printf("trying to connect to new network %q", addr)
878 if err := sanityCheckServer(addr); err != nil {
879 dc.logger.Printf("failed to connect to %q: %v", addr, err)
880 return ircError{&irc.Message{
881 Command: irc.ERR_PASSWDMISMATCH,
882 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
883 }}
884 }
885
886 // Some clients only allow specifying the nickname (and use the
887 // nickname as a username too). Strip the network name from the
888 // nickname when auto-saving networks.
889 nick, _, _ := unmarshalUsername(dc.nick)
890
891 dc.logger.Printf("auto-saving network %q", dc.networkName)
892 var err error
893 network, err = dc.user.createNetwork(&Network{
894 Addr: dc.networkName,
895 Nick: nick,
896 })
897 if err != nil {
898 return err
899 }
900 }
901
902 dc.network = network
903 return nil
904}
905
906func (dc *downstreamConn) welcome() error {
907 if dc.user == nil || !dc.registered {
908 panic("tried to welcome an unregistered connection")
909 }
910
911 // TODO: doing this might take some time. We should do it in dc.register
912 // instead, but we'll potentially be adding a new network and this must be
913 // done in the user goroutine.
914 if err := dc.loadNetwork(); err != nil {
915 return err
916 }
917
918 isupport := []string{
919 fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit),
920 "CASEMAPPING=ascii",
921 }
922
923 if uc := dc.upstream(); uc != nil {
924 for k := range passthroughIsupport {
925 v, ok := uc.isupport[k]
926 if !ok {
927 continue
928 }
929 if v != nil {
930 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
931 } else {
932 isupport = append(isupport, k)
933 }
934 }
935 }
936
937 dc.SendMessage(&irc.Message{
938 Prefix: dc.srv.prefix(),
939 Command: irc.RPL_WELCOME,
940 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
941 })
942 dc.SendMessage(&irc.Message{
943 Prefix: dc.srv.prefix(),
944 Command: irc.RPL_YOURHOST,
945 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
946 })
947 dc.SendMessage(&irc.Message{
948 Prefix: dc.srv.prefix(),
949 Command: irc.RPL_CREATED,
950 Params: []string{dc.nick, "Who cares when the server was created?"},
951 })
952 dc.SendMessage(&irc.Message{
953 Prefix: dc.srv.prefix(),
954 Command: irc.RPL_MYINFO,
955 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
956 })
957 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
958 dc.SendMessage(msg)
959 }
960 dc.SendMessage(&irc.Message{
961 Prefix: dc.srv.prefix(),
962 Command: irc.ERR_NOMOTD,
963 Params: []string{dc.nick, "No MOTD"},
964 })
965
966 dc.updateNick()
967 dc.updateSupportedCaps()
968
969 dc.forEachUpstream(func(uc *upstreamConn) {
970 for _, entry := range uc.channels.innerMap {
971 ch := entry.value.(*upstreamChannel)
972 if !ch.complete {
973 continue
974 }
975 record := uc.network.channels.Value(ch.Name)
976 if record != nil && record.Detached {
977 continue
978 }
979
980 dc.SendMessage(&irc.Message{
981 Prefix: dc.prefix(),
982 Command: "JOIN",
983 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
984 })
985
986 forwardChannel(dc, ch)
987 }
988 })
989
990 dc.forEachNetwork(func(net *network) {
991 // Only send history if we're the first connected client with that name
992 // for the network
993 if _, ok := net.offlineClients[dc.clientName]; ok {
994 dc.sendNetworkBacklog(net)
995 delete(net.offlineClients, dc.clientName)
996 }
997
998 // Fast-forward history to last message
999 for target, entry := range net.delivered.innerMap {
1000 delivered := entry.value.(map[string]string)
1001 ch := net.channels.Value(target)
1002 if ch != nil && ch.Detached {
1003 continue
1004 }
1005
1006 lastID, err := dc.user.msgStore.LastMsgID(net, target, time.Now())
1007 if err != nil {
1008 dc.logger.Printf("failed to get last message ID: %v", err)
1009 continue
1010 }
1011 delivered[dc.clientName] = lastID
1012 }
1013 })
1014
1015 return nil
1016}
1017
1018// messageSupportsHistory checks whether the provided message can be sent as
1019// part of an history batch.
1020func (dc *downstreamConn) messageSupportsHistory(msg *irc.Message) bool {
1021 // Don't replay all messages, because that would mess up client
1022 // state. For instance we just sent the list of users, sending
1023 // PART messages for one of these users would be incorrect.
1024 // TODO: add support for draft/event-playback
1025 switch msg.Command {
1026 case "PRIVMSG", "NOTICE":
1027 return true
1028 }
1029 return false
1030}
1031
1032func (dc *downstreamConn) sendNetworkBacklog(net *network) {
1033 for _, entry := range net.delivered.innerMap {
1034 target := entry.originalKey
1035 dc.sendTargetBacklog(net, target)
1036 }
1037}
1038
1039func (dc *downstreamConn) sendTargetBacklog(net *network, target string) {
1040 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1041 return
1042 }
1043 if ch := net.channels.Value(target); ch != nil && ch.Detached {
1044 return
1045 }
1046 delivered := net.delivered.Value(target)
1047 if delivered == nil {
1048 return
1049 }
1050 lastDelivered, ok := delivered[dc.clientName]
1051 if !ok {
1052 return
1053 }
1054
1055 limit := 4000
1056 history, err := dc.user.msgStore.LoadLatestID(net, target, lastDelivered, limit)
1057 if err != nil {
1058 dc.logger.Printf("failed to send implicit history for %q: %v", target, err)
1059 return
1060 }
1061
1062 batchRef := "history"
1063 if dc.caps["batch"] {
1064 dc.SendMessage(&irc.Message{
1065 Prefix: dc.srv.prefix(),
1066 Command: "BATCH",
1067 Params: []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
1068 })
1069 }
1070
1071 for _, msg := range history {
1072 if !dc.messageSupportsHistory(msg) {
1073 continue
1074 }
1075
1076 if dc.caps["batch"] {
1077 msg.Tags["batch"] = irc.TagValue(batchRef)
1078 }
1079 dc.SendMessage(dc.marshalMessage(msg, net))
1080 }
1081
1082 if dc.caps["batch"] {
1083 dc.SendMessage(&irc.Message{
1084 Prefix: dc.srv.prefix(),
1085 Command: "BATCH",
1086 Params: []string{"-" + batchRef},
1087 })
1088 }
1089}
1090
1091func (dc *downstreamConn) runUntilRegistered() error {
1092 for !dc.registered {
1093 msg, err := dc.ReadMessage()
1094 if err != nil {
1095 return fmt.Errorf("failed to read IRC command: %v", err)
1096 }
1097
1098 err = dc.handleMessage(msg)
1099 if ircErr, ok := err.(ircError); ok {
1100 ircErr.Message.Prefix = dc.srv.prefix()
1101 dc.SendMessage(ircErr.Message)
1102 } else if err != nil {
1103 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1104 }
1105 }
1106
1107 return nil
1108}
1109
1110func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
1111 switch msg.Command {
1112 case "CAP":
1113 var subCmd string
1114 if err := parseMessageParams(msg, &subCmd); err != nil {
1115 return err
1116 }
1117 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1118 return err
1119 }
1120 case "PING":
1121 var source, destination string
1122 if err := parseMessageParams(msg, &source); err != nil {
1123 return err
1124 }
1125 if len(msg.Params) > 1 {
1126 destination = msg.Params[1]
1127 }
1128 if destination != "" && destination != dc.srv.Hostname {
1129 return ircError{&irc.Message{
1130 Command: irc.ERR_NOSUCHSERVER,
1131 Params: []string{dc.nick, destination, "No such server"},
1132 }}
1133 }
1134 dc.SendMessage(&irc.Message{
1135 Prefix: dc.srv.prefix(),
1136 Command: "PONG",
1137 Params: []string{dc.srv.Hostname, source},
1138 })
1139 return nil
1140 case "PONG":
1141 if len(msg.Params) == 0 {
1142 return newNeedMoreParamsError(msg.Command)
1143 }
1144 token := msg.Params[len(msg.Params)-1]
1145 dc.handlePong(token)
1146 case "USER":
1147 return ircError{&irc.Message{
1148 Command: irc.ERR_ALREADYREGISTERED,
1149 Params: []string{dc.nick, "You may not reregister"},
1150 }}
1151 case "NICK":
1152 var rawNick string
1153 if err := parseMessageParams(msg, &rawNick); err != nil {
1154 return err
1155 }
1156
1157 nick := rawNick
1158 var upstream *upstreamConn
1159 if dc.upstream() == nil {
1160 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1161 if err == nil { // NICK nick/network: NICK only on a specific upstream
1162 upstream = uc
1163 nick = unmarshaledNick
1164 }
1165 }
1166
1167 if strings.ContainsAny(nick, illegalNickChars) {
1168 return ircError{&irc.Message{
1169 Command: irc.ERR_ERRONEUSNICKNAME,
1170 Params: []string{dc.nick, rawNick, "contains illegal characters"},
1171 }}
1172 }
1173 if casemapASCII(nick) == serviceNickCM {
1174 return ircError{&irc.Message{
1175 Command: irc.ERR_NICKNAMEINUSE,
1176 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1177 }}
1178 }
1179
1180 var err error
1181 dc.forEachNetwork(func(n *network) {
1182 if err != nil || (upstream != nil && upstream.network != n) {
1183 return
1184 }
1185 n.Nick = nick
1186 err = dc.srv.db.StoreNetwork(dc.user.ID, &n.Network)
1187 })
1188 if err != nil {
1189 return err
1190 }
1191
1192 dc.forEachUpstream(func(uc *upstreamConn) {
1193 if upstream != nil && upstream != uc {
1194 return
1195 }
1196 uc.SendMessageLabeled(dc.id, &irc.Message{
1197 Command: "NICK",
1198 Params: []string{nick},
1199 })
1200 })
1201
1202 if dc.upstream() == nil && dc.nick != nick {
1203 dc.SendMessage(&irc.Message{
1204 Prefix: dc.prefix(),
1205 Command: "NICK",
1206 Params: []string{nick},
1207 })
1208 dc.nick = nick
1209 dc.nickCM = casemapASCII(dc.nick)
1210 }
1211 case "JOIN":
1212 var namesStr string
1213 if err := parseMessageParams(msg, &namesStr); err != nil {
1214 return err
1215 }
1216
1217 var keys []string
1218 if len(msg.Params) > 1 {
1219 keys = strings.Split(msg.Params[1], ",")
1220 }
1221
1222 for i, name := range strings.Split(namesStr, ",") {
1223 uc, upstreamName, err := dc.unmarshalEntity(name)
1224 if err != nil {
1225 return err
1226 }
1227
1228 var key string
1229 if len(keys) > i {
1230 key = keys[i]
1231 }
1232
1233 params := []string{upstreamName}
1234 if key != "" {
1235 params = append(params, key)
1236 }
1237 uc.SendMessageLabeled(dc.id, &irc.Message{
1238 Command: "JOIN",
1239 Params: params,
1240 })
1241
1242 ch := uc.network.channels.Value(upstreamName)
1243 if ch != nil {
1244 // Don't clear the channel key if there's one set
1245 // TODO: add a way to unset the channel key
1246 if key != "" {
1247 ch.Key = key
1248 }
1249 uc.network.attach(ch)
1250 } else {
1251 ch = &Channel{
1252 Name: upstreamName,
1253 Key: key,
1254 }
1255 uc.network.channels.SetValue(upstreamName, ch)
1256 }
1257 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1258 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1259 }
1260 }
1261 case "PART":
1262 var namesStr string
1263 if err := parseMessageParams(msg, &namesStr); err != nil {
1264 return err
1265 }
1266
1267 var reason string
1268 if len(msg.Params) > 1 {
1269 reason = msg.Params[1]
1270 }
1271
1272 for _, name := range strings.Split(namesStr, ",") {
1273 uc, upstreamName, err := dc.unmarshalEntity(name)
1274 if err != nil {
1275 return err
1276 }
1277
1278 if strings.EqualFold(reason, "detach") {
1279 ch := uc.network.channels.Value(upstreamName)
1280 if ch != nil {
1281 uc.network.detach(ch)
1282 } else {
1283 ch = &Channel{
1284 Name: name,
1285 Detached: true,
1286 }
1287 uc.network.channels.SetValue(upstreamName, ch)
1288 }
1289 if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1290 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1291 }
1292 } else {
1293 params := []string{upstreamName}
1294 if reason != "" {
1295 params = append(params, reason)
1296 }
1297 uc.SendMessageLabeled(dc.id, &irc.Message{
1298 Command: "PART",
1299 Params: params,
1300 })
1301
1302 if err := uc.network.deleteChannel(upstreamName); err != nil {
1303 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1304 }
1305 }
1306 }
1307 case "KICK":
1308 var channelStr, userStr string
1309 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1310 return err
1311 }
1312
1313 channels := strings.Split(channelStr, ",")
1314 users := strings.Split(userStr, ",")
1315
1316 var reason string
1317 if len(msg.Params) > 2 {
1318 reason = msg.Params[2]
1319 }
1320
1321 if len(channels) != 1 && len(channels) != len(users) {
1322 return ircError{&irc.Message{
1323 Command: irc.ERR_BADCHANMASK,
1324 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1325 }}
1326 }
1327
1328 for i, user := range users {
1329 var channel string
1330 if len(channels) == 1 {
1331 channel = channels[0]
1332 } else {
1333 channel = channels[i]
1334 }
1335
1336 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1337 if err != nil {
1338 return err
1339 }
1340
1341 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1342 if err != nil {
1343 return err
1344 }
1345
1346 if ucChannel != ucUser {
1347 return ircError{&irc.Message{
1348 Command: irc.ERR_USERNOTINCHANNEL,
1349 Params: []string{dc.nick, user, channel, "They are on another network"},
1350 }}
1351 }
1352 uc := ucChannel
1353
1354 params := []string{upstreamChannel, upstreamUser}
1355 if reason != "" {
1356 params = append(params, reason)
1357 }
1358 uc.SendMessageLabeled(dc.id, &irc.Message{
1359 Command: "KICK",
1360 Params: params,
1361 })
1362 }
1363 case "MODE":
1364 var name string
1365 if err := parseMessageParams(msg, &name); err != nil {
1366 return err
1367 }
1368
1369 var modeStr string
1370 if len(msg.Params) > 1 {
1371 modeStr = msg.Params[1]
1372 }
1373
1374 if casemapASCII(name) == dc.nickCM {
1375 if modeStr != "" {
1376 dc.forEachUpstream(func(uc *upstreamConn) {
1377 uc.SendMessageLabeled(dc.id, &irc.Message{
1378 Command: "MODE",
1379 Params: []string{uc.nick, modeStr},
1380 })
1381 })
1382 } else {
1383 dc.SendMessage(&irc.Message{
1384 Prefix: dc.srv.prefix(),
1385 Command: irc.RPL_UMODEIS,
1386 Params: []string{dc.nick, ""}, // TODO
1387 })
1388 }
1389 return nil
1390 }
1391
1392 uc, upstreamName, err := dc.unmarshalEntity(name)
1393 if err != nil {
1394 return err
1395 }
1396
1397 if !uc.isChannel(upstreamName) {
1398 return ircError{&irc.Message{
1399 Command: irc.ERR_USERSDONTMATCH,
1400 Params: []string{dc.nick, "Cannot change mode for other users"},
1401 }}
1402 }
1403
1404 if modeStr != "" {
1405 params := []string{upstreamName, modeStr}
1406 params = append(params, msg.Params[2:]...)
1407 uc.SendMessageLabeled(dc.id, &irc.Message{
1408 Command: "MODE",
1409 Params: params,
1410 })
1411 } else {
1412 ch := uc.channels.Value(upstreamName)
1413 if ch == nil {
1414 return ircError{&irc.Message{
1415 Command: irc.ERR_NOSUCHCHANNEL,
1416 Params: []string{dc.nick, name, "No such channel"},
1417 }}
1418 }
1419
1420 if ch.modes == nil {
1421 // we haven't received the initial RPL_CHANNELMODEIS yet
1422 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1423 return nil
1424 }
1425
1426 modeStr, modeParams := ch.modes.Format()
1427 params := []string{dc.nick, name, modeStr}
1428 params = append(params, modeParams...)
1429
1430 dc.SendMessage(&irc.Message{
1431 Prefix: dc.srv.prefix(),
1432 Command: irc.RPL_CHANNELMODEIS,
1433 Params: params,
1434 })
1435 if ch.creationTime != "" {
1436 dc.SendMessage(&irc.Message{
1437 Prefix: dc.srv.prefix(),
1438 Command: rpl_creationtime,
1439 Params: []string{dc.nick, name, ch.creationTime},
1440 })
1441 }
1442 }
1443 case "TOPIC":
1444 var channel string
1445 if err := parseMessageParams(msg, &channel); err != nil {
1446 return err
1447 }
1448
1449 uc, upstreamName, err := dc.unmarshalEntity(channel)
1450 if err != nil {
1451 return err
1452 }
1453
1454 if len(msg.Params) > 1 { // setting topic
1455 topic := msg.Params[1]
1456 uc.SendMessageLabeled(dc.id, &irc.Message{
1457 Command: "TOPIC",
1458 Params: []string{upstreamName, topic},
1459 })
1460 } else { // getting topic
1461 ch := uc.channels.Value(upstreamName)
1462 if ch == nil {
1463 return ircError{&irc.Message{
1464 Command: irc.ERR_NOSUCHCHANNEL,
1465 Params: []string{dc.nick, upstreamName, "No such channel"},
1466 }}
1467 }
1468 sendTopic(dc, ch)
1469 }
1470 case "LIST":
1471 // TODO: support ELIST when supported by all upstreams
1472
1473 pl := pendingLIST{
1474 downstreamID: dc.id,
1475 pendingCommands: make(map[int64]*irc.Message),
1476 }
1477 var upstream *upstreamConn
1478 var upstreamChannels map[int64][]string
1479 if len(msg.Params) > 0 {
1480 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1481 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1482 upstream = uc
1483 } else {
1484 upstreamChannels = make(map[int64][]string)
1485 channels := strings.Split(msg.Params[0], ",")
1486 for _, channel := range channels {
1487 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1488 if err != nil {
1489 return err
1490 }
1491 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1492 }
1493 }
1494 }
1495
1496 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1497 dc.forEachUpstream(func(uc *upstreamConn) {
1498 if upstream != nil && upstream != uc {
1499 return
1500 }
1501 var params []string
1502 if upstreamChannels != nil {
1503 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1504 params = []string{strings.Join(channels, ",")}
1505 } else {
1506 return
1507 }
1508 }
1509 pl.pendingCommands[uc.network.ID] = &irc.Message{
1510 Command: "LIST",
1511 Params: params,
1512 }
1513 uc.trySendLIST(dc.id)
1514 })
1515 case "NAMES":
1516 if len(msg.Params) == 0 {
1517 dc.SendMessage(&irc.Message{
1518 Prefix: dc.srv.prefix(),
1519 Command: irc.RPL_ENDOFNAMES,
1520 Params: []string{dc.nick, "*", "End of /NAMES list"},
1521 })
1522 return nil
1523 }
1524
1525 channels := strings.Split(msg.Params[0], ",")
1526 for _, channel := range channels {
1527 uc, upstreamName, err := dc.unmarshalEntity(channel)
1528 if err != nil {
1529 return err
1530 }
1531
1532 ch := uc.channels.Value(upstreamName)
1533 if ch != nil {
1534 sendNames(dc, ch)
1535 } else {
1536 // NAMES on a channel we have not joined, ask upstream
1537 uc.SendMessageLabeled(dc.id, &irc.Message{
1538 Command: "NAMES",
1539 Params: []string{upstreamName},
1540 })
1541 }
1542 }
1543 case "WHO":
1544 if len(msg.Params) == 0 {
1545 // TODO: support WHO without parameters
1546 dc.SendMessage(&irc.Message{
1547 Prefix: dc.srv.prefix(),
1548 Command: irc.RPL_ENDOFWHO,
1549 Params: []string{dc.nick, "*", "End of /WHO list"},
1550 })
1551 return nil
1552 }
1553
1554 // TODO: support WHO masks
1555 entity := msg.Params[0]
1556 entityCM := casemapASCII(entity)
1557
1558 if entityCM == dc.nickCM {
1559 // TODO: support AWAY (H/G) in self WHO reply
1560 dc.SendMessage(&irc.Message{
1561 Prefix: dc.srv.prefix(),
1562 Command: irc.RPL_WHOREPLY,
1563 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1564 })
1565 dc.SendMessage(&irc.Message{
1566 Prefix: dc.srv.prefix(),
1567 Command: irc.RPL_ENDOFWHO,
1568 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1569 })
1570 return nil
1571 }
1572 if entityCM == serviceNickCM {
1573 dc.SendMessage(&irc.Message{
1574 Prefix: dc.srv.prefix(),
1575 Command: irc.RPL_WHOREPLY,
1576 Params: []string{serviceNick, "*", servicePrefix.User, servicePrefix.Host, dc.srv.Hostname, serviceNick, "H", "0 " + serviceRealname},
1577 })
1578 dc.SendMessage(&irc.Message{
1579 Prefix: dc.srv.prefix(),
1580 Command: irc.RPL_ENDOFWHO,
1581 Params: []string{dc.nick, serviceNick, "End of /WHO list"},
1582 })
1583 return nil
1584 }
1585
1586 uc, upstreamName, err := dc.unmarshalEntity(entity)
1587 if err != nil {
1588 return err
1589 }
1590
1591 var params []string
1592 if len(msg.Params) == 2 {
1593 params = []string{upstreamName, msg.Params[1]}
1594 } else {
1595 params = []string{upstreamName}
1596 }
1597
1598 uc.SendMessageLabeled(dc.id, &irc.Message{
1599 Command: "WHO",
1600 Params: params,
1601 })
1602 case "WHOIS":
1603 if len(msg.Params) == 0 {
1604 return ircError{&irc.Message{
1605 Command: irc.ERR_NONICKNAMEGIVEN,
1606 Params: []string{dc.nick, "No nickname given"},
1607 }}
1608 }
1609
1610 var target, mask string
1611 if len(msg.Params) == 1 {
1612 target = ""
1613 mask = msg.Params[0]
1614 } else {
1615 target = msg.Params[0]
1616 mask = msg.Params[1]
1617 }
1618 // TODO: support multiple WHOIS users
1619 if i := strings.IndexByte(mask, ','); i >= 0 {
1620 mask = mask[:i]
1621 }
1622
1623 if casemapASCII(mask) == dc.nickCM {
1624 dc.SendMessage(&irc.Message{
1625 Prefix: dc.srv.prefix(),
1626 Command: irc.RPL_WHOISUSER,
1627 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1628 })
1629 dc.SendMessage(&irc.Message{
1630 Prefix: dc.srv.prefix(),
1631 Command: irc.RPL_WHOISSERVER,
1632 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1633 })
1634 dc.SendMessage(&irc.Message{
1635 Prefix: dc.srv.prefix(),
1636 Command: irc.RPL_ENDOFWHOIS,
1637 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1638 })
1639 return nil
1640 }
1641
1642 // TODO: support WHOIS masks
1643 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1644 if err != nil {
1645 return err
1646 }
1647
1648 var params []string
1649 if target != "" {
1650 if target == mask { // WHOIS nick nick
1651 params = []string{upstreamNick, upstreamNick}
1652 } else {
1653 params = []string{target, upstreamNick}
1654 }
1655 } else {
1656 params = []string{upstreamNick}
1657 }
1658
1659 uc.SendMessageLabeled(dc.id, &irc.Message{
1660 Command: "WHOIS",
1661 Params: params,
1662 })
1663 case "PRIVMSG":
1664 var targetsStr, text string
1665 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1666 return err
1667 }
1668 tags := copyClientTags(msg.Tags)
1669
1670 for _, name := range strings.Split(targetsStr, ",") {
1671 if name == serviceNick {
1672 if dc.caps["echo-message"] {
1673 echoTags := tags.Copy()
1674 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1675 dc.SendMessage(&irc.Message{
1676 Tags: echoTags,
1677 Prefix: dc.prefix(),
1678 Command: "PRIVMSG",
1679 Params: []string{name, text},
1680 })
1681 }
1682 handleServicePRIVMSG(dc, text)
1683 continue
1684 }
1685
1686 uc, upstreamName, err := dc.unmarshalEntity(name)
1687 if err != nil {
1688 return err
1689 }
1690
1691 if upstreamName == "NickServ" {
1692 dc.handleNickServPRIVMSG(uc, text)
1693 }
1694
1695 unmarshaledText := text
1696 if uc.isChannel(upstreamName) {
1697 unmarshaledText = dc.unmarshalText(uc, text)
1698 }
1699 uc.SendMessageLabeled(dc.id, &irc.Message{
1700 Tags: tags,
1701 Command: "PRIVMSG",
1702 Params: []string{upstreamName, unmarshaledText},
1703 })
1704
1705 echoTags := tags.Copy()
1706 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1707 echoMsg := &irc.Message{
1708 Tags: echoTags,
1709 Prefix: &irc.Prefix{
1710 Name: uc.nick,
1711 User: uc.username,
1712 },
1713 Command: "PRIVMSG",
1714 Params: []string{upstreamName, text},
1715 }
1716 uc.produce(upstreamName, echoMsg, dc)
1717
1718 uc.updateChannelAutoDetach(upstreamName)
1719 }
1720 case "NOTICE":
1721 var targetsStr, text string
1722 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1723 return err
1724 }
1725 tags := copyClientTags(msg.Tags)
1726
1727 for _, name := range strings.Split(targetsStr, ",") {
1728 uc, upstreamName, err := dc.unmarshalEntity(name)
1729 if err != nil {
1730 return err
1731 }
1732
1733 unmarshaledText := text
1734 if uc.isChannel(upstreamName) {
1735 unmarshaledText = dc.unmarshalText(uc, text)
1736 }
1737 uc.SendMessageLabeled(dc.id, &irc.Message{
1738 Tags: tags,
1739 Command: "NOTICE",
1740 Params: []string{upstreamName, unmarshaledText},
1741 })
1742
1743 uc.updateChannelAutoDetach(upstreamName)
1744 }
1745 case "TAGMSG":
1746 var targetsStr string
1747 if err := parseMessageParams(msg, &targetsStr); err != nil {
1748 return err
1749 }
1750 tags := copyClientTags(msg.Tags)
1751
1752 for _, name := range strings.Split(targetsStr, ",") {
1753 uc, upstreamName, err := dc.unmarshalEntity(name)
1754 if err != nil {
1755 return err
1756 }
1757 if _, ok := uc.caps["message-tags"]; !ok {
1758 continue
1759 }
1760
1761 uc.SendMessageLabeled(dc.id, &irc.Message{
1762 Tags: tags,
1763 Command: "TAGMSG",
1764 Params: []string{upstreamName},
1765 })
1766
1767 uc.updateChannelAutoDetach(upstreamName)
1768 }
1769 case "INVITE":
1770 var user, channel string
1771 if err := parseMessageParams(msg, &user, &channel); err != nil {
1772 return err
1773 }
1774
1775 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1776 if err != nil {
1777 return err
1778 }
1779
1780 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1781 if err != nil {
1782 return err
1783 }
1784
1785 if ucChannel != ucUser {
1786 return ircError{&irc.Message{
1787 Command: irc.ERR_USERNOTINCHANNEL,
1788 Params: []string{dc.nick, user, channel, "They are on another network"},
1789 }}
1790 }
1791 uc := ucChannel
1792
1793 uc.SendMessageLabeled(dc.id, &irc.Message{
1794 Command: "INVITE",
1795 Params: []string{upstreamUser, upstreamChannel},
1796 })
1797 case "CHATHISTORY":
1798 var subcommand string
1799 if err := parseMessageParams(msg, &subcommand); err != nil {
1800 return err
1801 }
1802 var target, criteria, limitStr string
1803 if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil {
1804 return ircError{&irc.Message{
1805 Command: "FAIL",
1806 Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
1807 }}
1808 }
1809
1810 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
1811 if !ok {
1812 return ircError{&irc.Message{
1813 Command: irc.ERR_UNKNOWNCOMMAND,
1814 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
1815 }}
1816 }
1817
1818 uc, entity, err := dc.unmarshalEntity(target)
1819 if err != nil {
1820 return err
1821 }
1822
1823 // TODO: support msgid criteria
1824 criteriaParts := strings.SplitN(criteria, "=", 2)
1825 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
1826 return ircError{&irc.Message{
1827 Command: "FAIL",
1828 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Unknown criteria"},
1829 }}
1830 }
1831
1832 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
1833 if err != nil {
1834 return ircError{&irc.Message{
1835 Command: "FAIL",
1836 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Invalid criteria"},
1837 }}
1838 }
1839
1840 limit, err := strconv.Atoi(limitStr)
1841 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
1842 return ircError{&irc.Message{
1843 Command: "FAIL",
1844 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
1845 }}
1846 }
1847
1848 var history []*irc.Message
1849 switch subcommand {
1850 case "BEFORE":
1851 history, err = store.LoadBeforeTime(uc.network, entity, timestamp, limit)
1852 case "AFTER":
1853 history, err = store.LoadAfterTime(uc.network, entity, timestamp, limit)
1854 default:
1855 // TODO: support LATEST, BETWEEN
1856 return ircError{&irc.Message{
1857 Command: "FAIL",
1858 Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
1859 }}
1860 }
1861 if err != nil {
1862 dc.logger.Printf("failed parsing log messages for chathistory: %v", err)
1863 return newChatHistoryError(subcommand, target)
1864 }
1865
1866 batchRef := "history"
1867 dc.SendMessage(&irc.Message{
1868 Prefix: dc.srv.prefix(),
1869 Command: "BATCH",
1870 Params: []string{"+" + batchRef, "chathistory", target},
1871 })
1872
1873 for _, msg := range history {
1874 msg.Tags["batch"] = irc.TagValue(batchRef)
1875 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1876 }
1877
1878 dc.SendMessage(&irc.Message{
1879 Prefix: dc.srv.prefix(),
1880 Command: "BATCH",
1881 Params: []string{"-" + batchRef},
1882 })
1883 default:
1884 dc.logger.Printf("unhandled message: %v", msg)
1885 return newUnknownCommandError(msg.Command)
1886 }
1887 return nil
1888}
1889
1890func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1891 username, password, ok := parseNickServCredentials(text, uc.nick)
1892 if !ok {
1893 return
1894 }
1895
1896 // User may have e.g. EXTERNAL mechanism configured. We do not want to
1897 // automatically erase the key pair or any other credentials.
1898 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" {
1899 return
1900 }
1901
1902 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1903 n := uc.network
1904 n.SASL.Mechanism = "PLAIN"
1905 n.SASL.Plain.Username = username
1906 n.SASL.Plain.Password = password
1907 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
1908 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1909 }
1910}
1911
1912func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1913 fields := strings.Fields(text)
1914 if len(fields) < 2 {
1915 return "", "", false
1916 }
1917 cmd := strings.ToUpper(fields[0])
1918 params := fields[1:]
1919 switch cmd {
1920 case "REGISTER":
1921 username = nick
1922 password = params[0]
1923 case "IDENTIFY":
1924 if len(params) == 1 {
1925 username = nick
1926 password = params[0]
1927 } else {
1928 username = params[0]
1929 password = params[1]
1930 }
1931 case "SET":
1932 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1933 username = nick
1934 password = params[1]
1935 }
1936 default:
1937 return "", "", false
1938 }
1939 return username, password, true
1940}
Note: See TracBrowser for help on using the repository browser.