source: code/trunk/upstream.go@ 442

Last change on this file since 442 was 435, checked in by delthas, 4 years ago

Add customizable auto-detaching, auto-reattaching, relaying.

This uses the fields added previously to the Channel struct to implement
the actual detaching/reattaching/relaying logic.

The FilterDefault values of the messages filters are currently
hardcoded.

The values of the message filters are not currently user-settable.

This introduces a new user event, eventChannelDetach, which stores an
upstreamConn (which might become invalid at the time of processing), and
a channel name, used for auto-detaching. Every time the channel detach
timer is refreshed (by receveing a message, etc.), a new timer is
created on the upstreamChannel, which will dispatch this event after the
duration (and discards the previous timer, if any).

File size: 44.3 KB
RevLine 
[98]1package soju
[13]2
3import (
[307]4 "crypto"
5 "crypto/sha256"
[13]6 "crypto/tls"
[307]7 "crypto/x509"
[95]8 "encoding/base64"
[155]9 "errors"
[13]10 "fmt"
11 "io"
12 "net"
[352]13 "net/url"
[19]14 "strconv"
[17]15 "strings"
[19]16 "time"
[288]17 "unicode"
18 "unicode/utf8"
[13]19
[95]20 "github.com/emersion/go-sasl"
[13]21 "gopkg.in/irc.v3"
22)
23
[282]24// permanentUpstreamCaps is the static list of upstream capabilities always
25// requested when supported.
26var permanentUpstreamCaps = map[string]bool{
27 "away-notify": true,
28 "batch": true,
[419]29 "extended-join": true,
[282]30 "labeled-response": true,
31 "message-tags": true,
[292]32 "multi-prefix": true,
[282]33 "server-time": true,
34}
35
[399]36type registrationError string
37
38func (err registrationError) Error() string {
39 return fmt.Sprintf("registration error: %v", string(err))
40}
41
[19]42type upstreamChannel struct {
[162]43 Name string
44 conn *upstreamConn
45 Topic string
[405]46 TopicWho *irc.Prefix
[162]47 TopicTime time.Time
48 Status channelStatus
49 modes channelModes
50 creationTime string
[292]51 Members map[string]*memberships
[162]52 complete bool
[435]53 detachTimer *time.Timer
[19]54}
55
[435]56func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) {
57 if uc.detachTimer != nil {
58 uc.detachTimer.Stop()
59 uc.detachTimer = nil
60 }
61
62 if dur == 0 {
63 return
64 }
65
66 uc.detachTimer = time.AfterFunc(dur, func() {
67 uc.conn.network.user.events <- eventChannelDetach{
68 uc: uc.conn,
69 name: uc.Name,
70 }
71 })
72}
73
[13]74type upstreamConn struct {
[210]75 conn
[16]76
[210]77 network *network
78 user *user
79
[16]80 serverName string
81 availableUserModes string
[139]82 availableChannelModes map[byte]channelModeType
83 availableChannelTypes string
84 availableMemberships []membership
[19]85
[277]86 registered bool
87 nick string
88 username string
89 realname string
90 modes userModes
91 channels map[string]*upstreamChannel
92 supportedCaps map[string]string
[278]93 caps map[string]bool
[277]94 batches map[string]batch
95 away bool
[278]96 nextLabelID uint64
[95]97
98 saslClient sasl.Client
99 saslStarted bool
[177]100
101 // set of LIST commands in progress, per downstream
102 pendingLISTDownstreamSet map[uint64]struct{}
[13]103}
104
[77]105func connectToUpstream(network *network) (*upstreamConn, error) {
106 logger := &prefixLogger{network.user.srv.Logger, fmt.Sprintf("upstream %q: ", network.Addr)}
[33]107
[352]108 dialer := net.Dialer{Timeout: connectTimeout}
[269]109
[352]110 s := network.Addr
111 if !strings.Contains(s, "://") {
112 // This is a raw domain name, make it an URL with the default scheme
113 s = "ircs://" + s
[77]114 }
115
[352]116 u, err := url.Parse(s)
117 if err != nil {
118 return nil, fmt.Errorf("failed to parse upstream server URL: %v", err)
119 }
[206]120
[269]121 var netConn net.Conn
[352]122 switch u.Scheme {
[269]123 case "ircs":
[352]124 addr := u.Host
[381]125 host, _, err := net.SplitHostPort(u.Host)
126 if err != nil {
127 host = u.Host
128 addr = u.Host + ":6697"
[269]129 }
130
131 logger.Printf("connecting to TLS server at address %q", addr)
[307]132
[381]133 tlsConfig := &tls.Config{ServerName: host}
[307]134 if network.SASL.Mechanism == "EXTERNAL" {
135 if network.SASL.External.CertBlob == nil {
136 return nil, fmt.Errorf("missing certificate for authentication")
137 }
138 if network.SASL.External.PrivKeyBlob == nil {
139 return nil, fmt.Errorf("missing private key for authentication")
140 }
141 key, err := x509.ParsePKCS8PrivateKey(network.SASL.External.PrivKeyBlob)
142 if err != nil {
143 return nil, fmt.Errorf("failed to parse private key: %v", err)
144 }
[381]145 tlsConfig.Certificates = []tls.Certificate{
146 {
147 Certificate: [][]byte{network.SASL.External.CertBlob},
148 PrivateKey: key.(crypto.PrivateKey),
[307]149 },
150 }
151 logger.Printf("using TLS client certificate %x", sha256.Sum256(network.SASL.External.CertBlob))
152 }
153
[381]154 netConn, err = dialer.Dial("tcp", addr)
[352]155 if err != nil {
156 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
157 }
[381]158
159 // Don't do the TLS handshake immediately, because we need to register
160 // the new connection with identd ASAP. See:
161 // https://todo.sr.ht/~emersion/soju/69#event-41859
162 netConn = tls.Client(netConn, tlsConfig)
[270]163 case "irc+insecure":
[352]164 addr := u.Host
[351]165 if _, _, err := net.SplitHostPort(addr); err != nil {
[270]166 addr = addr + ":6667"
167 }
168
169 logger.Printf("connecting to plain-text server at address %q", addr)
170 netConn, err = dialer.Dial("tcp", addr)
[352]171 if err != nil {
172 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
173 }
[369]174 case "irc+unix", "unix":
[353]175 logger.Printf("connecting to Unix socket at path %q", u.Path)
176 netConn, err = dialer.Dial("unix", u.Path)
177 if err != nil {
178 return nil, fmt.Errorf("failed to connect to Unix socket %q: %v", u.Path, err)
179 }
[269]180 default:
[352]181 return nil, fmt.Errorf("failed to dial %q: unknown scheme: %v", network.Addr, u.Scheme)
[269]182 }
[33]183
[398]184 options := connOptions{
[402]185 Logger: logger,
[398]186 RateLimitDelay: upstreamMessageDelay,
187 RateLimitBurst: upstreamMessageBurst,
188 }
189
[55]190 uc := &upstreamConn{
[398]191 conn: *newConn(network.user.srv, newNetIRCConn(netConn), &options),
[177]192 network: network,
193 user: network.user,
194 channels: make(map[string]*upstreamChannel),
[277]195 supportedCaps: make(map[string]string),
[278]196 caps: make(map[string]bool),
[177]197 batches: make(map[string]batch),
198 availableChannelTypes: stdChannelTypes,
199 availableChannelModes: stdChannelModes,
200 availableMemberships: stdMemberships,
201 pendingLISTDownstreamSet: make(map[uint64]struct{}),
[33]202 }
[55]203 return uc, nil
[33]204}
205
[73]206func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) {
[218]207 uc.network.forEachDownstream(f)
[73]208}
209
[161]210func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) {
[155]211 uc.forEachDownstream(func(dc *downstreamConn) {
212 if id != 0 && id != dc.id {
213 return
214 }
215 f(dc)
216 })
217}
218
[55]219func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
220 ch, ok := uc.channels[name]
[19]221 if !ok {
222 return nil, fmt.Errorf("unknown channel %q", name)
223 }
224 return ch, nil
225}
226
[129]227func (uc *upstreamConn) isChannel(entity string) bool {
[139]228 if i := strings.IndexByte(uc.availableChannelTypes, entity[0]); i >= 0 {
229 return true
[129]230 }
231 return false
232}
233
[181]234func (uc *upstreamConn) getPendingLIST() *pendingLIST {
[177]235 for _, pl := range uc.user.pendingLISTs {
236 if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
237 continue
238 }
239 return &pl
240 }
241 return nil
242}
243
[181]244func (uc *upstreamConn) endPendingLISTs(all bool) (found bool) {
[177]245 found = false
246 for i := 0; i < len(uc.user.pendingLISTs); i++ {
247 pl := uc.user.pendingLISTs[i]
248 if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
249 continue
250 }
251 delete(pl.pendingCommands, uc.network.ID)
252 if len(pl.pendingCommands) == 0 {
253 uc.user.pendingLISTs = append(uc.user.pendingLISTs[:i], uc.user.pendingLISTs[i+1:]...)
254 i--
255 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
256 dc.SendMessage(&irc.Message{
257 Prefix: dc.srv.prefix(),
258 Command: irc.RPL_LISTEND,
259 Params: []string{dc.nick, "End of /LIST"},
260 })
261 })
262 }
263 found = true
264 if !all {
265 delete(uc.pendingLISTDownstreamSet, pl.downstreamID)
266 uc.user.forEachUpstream(func(uc *upstreamConn) {
[181]267 uc.trySendLIST(pl.downstreamID)
[177]268 })
269 return
270 }
271 }
272 return
273}
274
[181]275func (uc *upstreamConn) trySendLIST(downstreamID uint64) {
[177]276 if _, ok := uc.pendingLISTDownstreamSet[downstreamID]; ok {
277 // a LIST command is already pending
278 // we will try again when that command is completed
279 return
280 }
281
282 for _, pl := range uc.user.pendingLISTs {
283 if pl.downstreamID != downstreamID {
284 continue
285 }
286 // this is the first pending LIST command list of the downstream
287 listCommand, ok := pl.pendingCommands[uc.network.ID]
288 if !ok {
289 // there is no command for this upstream in these LIST commands
290 // do not send anything
291 continue
292 }
293 // there is a command for this upstream in these LIST commands
294 // send it now
295
296 uc.SendMessageLabeled(downstreamID, listCommand)
297
298 uc.pendingLISTDownstreamSet[downstreamID] = struct{}{}
299 return
300 }
301}
302
[292]303func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) {
304 memberships := make(memberships, 0, 4)
305 i := 0
[139]306 for _, m := range uc.availableMemberships {
[292]307 if i >= len(s) {
308 break
[139]309 }
[292]310 if s[i] == m.Prefix {
311 memberships = append(memberships, m)
312 i++
313 }
[139]314 }
[292]315 return &memberships, s[i:]
[139]316}
317
[288]318func isWordBoundary(r rune) bool {
319 switch r {
320 case '-', '_', '|':
321 return false
322 case '\u00A0':
323 return true
324 default:
325 return !unicode.IsLetter(r) && !unicode.IsNumber(r)
326 }
327}
328
329func isHighlight(text, nick string) bool {
330 for {
331 i := strings.Index(text, nick)
332 if i < 0 {
333 return false
334 }
335
336 // Detect word boundaries
337 var left, right rune
338 if i > 0 {
339 left, _ = utf8.DecodeLastRuneInString(text[:i])
340 }
341 if i < len(text) {
342 right, _ = utf8.DecodeRuneInString(text[i+len(nick):])
343 }
344 if isWordBoundary(left) && isWordBoundary(right) {
345 return true
346 }
347
348 text = text[i+len(nick):]
349 }
350}
351
[55]352func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
[155]353 var label string
354 if l, ok := msg.GetTag("label"); ok {
355 label = l
356 }
357
[153]358 var msgBatch *batch
359 if batchName, ok := msg.GetTag("batch"); ok {
360 b, ok := uc.batches[batchName]
361 if !ok {
362 return fmt.Errorf("unexpected batch reference: batch was not defined: %q", batchName)
363 }
364 msgBatch = &b
[155]365 if label == "" {
366 label = msgBatch.Label
367 }
[153]368 }
369
[161]370 var downstreamID uint64 = 0
[155]371 if label != "" {
372 var labelOffset uint64
[161]373 n, err := fmt.Sscanf(label, "sd-%d-%d", &downstreamID, &labelOffset)
[155]374 if err == nil && n < 2 {
375 err = errors.New("not enough arguments")
376 }
377 if err != nil {
378 return fmt.Errorf("unexpected message label: invalid downstream reference for label %q: %v", label, err)
379 }
380 }
381
[216]382 if _, ok := msg.Tags["time"]; !ok {
[240]383 msg.Tags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
[216]384 }
385
[13]386 switch msg.Command {
387 case "PING":
[60]388 uc.SendMessage(&irc.Message{
[13]389 Command: "PONG",
[68]390 Params: msg.Params,
[60]391 })
[33]392 return nil
[303]393 case "NOTICE", "PRIVMSG", "TAGMSG":
[273]394 if msg.Prefix == nil {
395 return fmt.Errorf("expected a prefix")
396 }
397
[286]398 var entity, text string
[303]399 if msg.Command != "TAGMSG" {
400 if err := parseMessageParams(msg, &entity, &text); err != nil {
401 return err
402 }
403 } else {
404 if err := parseMessageParams(msg, &entity); err != nil {
405 return err
406 }
[286]407 }
408
409 if msg.Prefix.Name == serviceNick {
410 uc.logger.Printf("skipping %v from soju's service: %v", msg.Command, msg)
411 break
412 }
413 if entity == serviceNick {
414 uc.logger.Printf("skipping %v to soju's service: %v", msg.Command, msg)
415 break
416 }
417
[171]418 if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
[239]419 uc.produce("", msg, nil)
[303]420 } else { // regular user message
[217]421 target := entity
422 if target == uc.nick {
[178]423 target = msg.Prefix.Name
424 }
[287]425
[435]426 if ch, ok := uc.network.channels[target]; ok {
427 if ch.Detached {
428 uc.handleDetachedMessage(msg.Prefix.Name, text, ch)
429 }
430
431 highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick)
432 if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
433 uc.updateChannelAutoDetach(target)
434 }
[287]435 }
[435]436
437 uc.produce(target, msg, nil)
[171]438 }
[92]439 case "CAP":
[95]440 var subCmd string
441 if err := parseMessageParams(msg, nil, &subCmd); err != nil {
442 return err
[92]443 }
[95]444 subCmd = strings.ToUpper(subCmd)
445 subParams := msg.Params[2:]
446 switch subCmd {
447 case "LS":
448 if len(subParams) < 1 {
449 return newNeedMoreParamsError(msg.Command)
450 }
[281]451 caps := subParams[len(subParams)-1]
[95]452 more := len(subParams) >= 2 && msg.Params[len(subParams)-2] == "*"
[92]453
[281]454 uc.handleSupportedCaps(caps)
[92]455
[95]456 if more {
457 break // wait to receive all capabilities
458 }
459
[281]460 uc.requestCaps()
[152]461
[95]462 if uc.requestSASL() {
463 break // we'll send CAP END after authentication is completed
464 }
465
[92]466 uc.SendMessage(&irc.Message{
467 Command: "CAP",
468 Params: []string{"END"},
469 })
[95]470 case "ACK", "NAK":
471 if len(subParams) < 1 {
472 return newNeedMoreParamsError(msg.Command)
473 }
474 caps := strings.Fields(subParams[0])
475
476 for _, name := range caps {
477 if err := uc.handleCapAck(strings.ToLower(name), subCmd == "ACK"); err != nil {
478 return err
479 }
480 }
481
[281]482 if uc.registered {
483 uc.forEachDownstream(func(dc *downstreamConn) {
484 dc.updateSupportedCaps()
485 })
486 }
487 case "NEW":
488 if len(subParams) < 1 {
489 return newNeedMoreParamsError(msg.Command)
490 }
491 uc.handleSupportedCaps(subParams[0])
492 uc.requestCaps()
493 case "DEL":
494 if len(subParams) < 1 {
495 return newNeedMoreParamsError(msg.Command)
496 }
497 caps := strings.Fields(subParams[0])
498
499 for _, c := range caps {
500 delete(uc.supportedCaps, c)
501 delete(uc.caps, c)
502 }
503
504 if uc.registered {
505 uc.forEachDownstream(func(dc *downstreamConn) {
506 dc.updateSupportedCaps()
507 })
508 }
[95]509 default:
510 uc.logger.Printf("unhandled message: %v", msg)
[92]511 }
[95]512 case "AUTHENTICATE":
513 if uc.saslClient == nil {
514 return fmt.Errorf("received unexpected AUTHENTICATE message")
515 }
516
517 // TODO: if a challenge is 400 bytes long, buffer it
518 var challengeStr string
519 if err := parseMessageParams(msg, &challengeStr); err != nil {
520 uc.SendMessage(&irc.Message{
521 Command: "AUTHENTICATE",
522 Params: []string{"*"},
523 })
524 return err
525 }
526
527 var challenge []byte
528 if challengeStr != "+" {
529 var err error
530 challenge, err = base64.StdEncoding.DecodeString(challengeStr)
531 if err != nil {
532 uc.SendMessage(&irc.Message{
533 Command: "AUTHENTICATE",
534 Params: []string{"*"},
535 })
536 return err
537 }
538 }
539
540 var resp []byte
541 var err error
542 if !uc.saslStarted {
543 _, resp, err = uc.saslClient.Start()
544 uc.saslStarted = true
545 } else {
546 resp, err = uc.saslClient.Next(challenge)
547 }
548 if err != nil {
549 uc.SendMessage(&irc.Message{
550 Command: "AUTHENTICATE",
551 Params: []string{"*"},
552 })
553 return err
554 }
555
556 // TODO: send response in multiple chunks if >= 400 bytes
557 var respStr = "+"
[318]558 if len(resp) != 0 {
[95]559 respStr = base64.StdEncoding.EncodeToString(resp)
560 }
561
562 uc.SendMessage(&irc.Message{
563 Command: "AUTHENTICATE",
564 Params: []string{respStr},
565 })
[125]566 case irc.RPL_LOGGEDIN:
[95]567 var account string
568 if err := parseMessageParams(msg, nil, nil, &account); err != nil {
569 return err
570 }
571 uc.logger.Printf("logged in with account %q", account)
[125]572 case irc.RPL_LOGGEDOUT:
[95]573 uc.logger.Printf("logged out")
[125]574 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
[95]575 var info string
576 if err := parseMessageParams(msg, nil, &info); err != nil {
577 return err
578 }
579 switch msg.Command {
[125]580 case irc.ERR_NICKLOCKED:
[95]581 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
[125]582 case irc.ERR_SASLFAIL:
[95]583 uc.logger.Printf("SASL authentication failed: %v", info)
[125]584 case irc.ERR_SASLTOOLONG:
[95]585 uc.logger.Printf("SASL message too long: %v", info)
586 }
587
588 uc.saslClient = nil
589 uc.saslStarted = false
590
591 uc.SendMessage(&irc.Message{
592 Command: "CAP",
593 Params: []string{"END"},
594 })
[14]595 case irc.RPL_WELCOME:
[55]596 uc.registered = true
597 uc.logger.Printf("connection registered")
[19]598
[276]599 uc.forEachDownstream(func(dc *downstreamConn) {
600 dc.updateSupportedCaps()
601 })
602
[310]603 if len(uc.network.channels) > 0 {
[350]604 var channels, keys []string
[310]605 for _, ch := range uc.network.channels {
[350]606 channels = append(channels, ch.Name)
[310]607 keys = append(keys, ch.Key)
608 }
[350]609
610 for _, msg := range join(channels, keys) {
611 uc.SendMessage(msg)
612 }
[19]613 }
[16]614 case irc.RPL_MYINFO:
[139]615 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
[43]616 return err
[16]617 }
[139]618 case irc.RPL_ISUPPORT:
619 if err := parseMessageParams(msg, nil, nil); err != nil {
620 return err
[16]621 }
[139]622 for _, token := range msg.Params[1 : len(msg.Params)-1] {
623 negate := false
624 parameter := token
625 value := ""
626 if strings.HasPrefix(token, "-") {
627 negate = true
628 token = token[1:]
629 } else {
630 if i := strings.IndexByte(token, '='); i >= 0 {
631 parameter = token[:i]
632 value = token[i+1:]
633 }
634 }
635 if !negate {
636 switch parameter {
637 case "CHANMODES":
638 parts := strings.SplitN(value, ",", 5)
639 if len(parts) < 4 {
640 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", value)
641 }
642 modes := make(map[byte]channelModeType)
643 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
644 for j := 0; j < len(parts[i]); j++ {
645 mode := parts[i][j]
646 modes[mode] = mt
647 }
648 }
649 uc.availableChannelModes = modes
650 case "CHANTYPES":
651 uc.availableChannelTypes = value
652 case "PREFIX":
653 if value == "" {
654 uc.availableMemberships = nil
655 } else {
656 if value[0] != '(' {
657 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
658 }
659 sep := strings.IndexByte(value, ')')
660 if sep < 0 || len(value) != sep*2 {
661 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
662 }
663 memberships := make([]membership, len(value)/2-1)
664 for i := range memberships {
665 memberships[i] = membership{
666 Mode: value[i+1],
667 Prefix: value[sep+i+1],
668 }
669 }
670 uc.availableMemberships = memberships
671 }
672 }
673 } else {
674 // TODO: handle ISUPPORT negations
675 }
676 }
[153]677 case "BATCH":
678 var tag string
679 if err := parseMessageParams(msg, &tag); err != nil {
680 return err
681 }
682
683 if strings.HasPrefix(tag, "+") {
684 tag = tag[1:]
685 if _, ok := uc.batches[tag]; ok {
686 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
687 }
688 var batchType string
689 if err := parseMessageParams(msg, nil, &batchType); err != nil {
690 return err
691 }
[155]692 label := label
693 if label == "" && msgBatch != nil {
694 label = msgBatch.Label
695 }
[153]696 uc.batches[tag] = batch{
697 Type: batchType,
698 Params: msg.Params[2:],
699 Outer: msgBatch,
[155]700 Label: label,
[153]701 }
702 } else if strings.HasPrefix(tag, "-") {
703 tag = tag[1:]
704 if _, ok := uc.batches[tag]; !ok {
705 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
706 }
707 delete(uc.batches, tag)
708 } else {
709 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
710 }
[42]711 case "NICK":
[83]712 if msg.Prefix == nil {
713 return fmt.Errorf("expected a prefix")
714 }
715
[43]716 var newNick string
717 if err := parseMessageParams(msg, &newNick); err != nil {
718 return err
[42]719 }
720
[244]721 me := false
[55]722 if msg.Prefix.Name == uc.nick {
723 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
[244]724 me = true
[55]725 uc.nick = newNick
[42]726 }
727
[55]728 for _, ch := range uc.channels {
[292]729 if memberships, ok := ch.Members[msg.Prefix.Name]; ok {
[42]730 delete(ch.Members, msg.Prefix.Name)
[292]731 ch.Members[newNick] = memberships
[215]732 uc.appendLog(ch.Name, msg)
[42]733 }
734 }
[82]735
[244]736 if !me {
[82]737 uc.forEachDownstream(func(dc *downstreamConn) {
[261]738 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[82]739 })
[296]740 } else {
741 uc.forEachDownstream(func(dc *downstreamConn) {
742 dc.updateNick()
743 })
[82]744 }
[69]745 case "JOIN":
746 if msg.Prefix == nil {
747 return fmt.Errorf("expected a prefix")
748 }
[42]749
[43]750 var channels string
751 if err := parseMessageParams(msg, &channels); err != nil {
752 return err
[19]753 }
[34]754
[43]755 for _, ch := range strings.Split(channels, ",") {
[55]756 if msg.Prefix.Name == uc.nick {
757 uc.logger.Printf("joined channel %q", ch)
758 uc.channels[ch] = &upstreamChannel{
[34]759 Name: ch,
[55]760 conn: uc,
[292]761 Members: make(map[string]*memberships),
[34]762 }
[435]763 uc.updateChannelAutoDetach(ch)
[139]764
765 uc.SendMessage(&irc.Message{
766 Command: "MODE",
767 Params: []string{ch},
768 })
[34]769 } else {
[55]770 ch, err := uc.getChannel(ch)
[34]771 if err != nil {
772 return err
773 }
[294]774 ch.Members[msg.Prefix.Name] = &memberships{}
[19]775 }
[69]776
[245]777 chMsg := msg.Copy()
778 chMsg.Params[0] = ch
779 uc.produce(ch, chMsg, nil)
[19]780 }
[69]781 case "PART":
782 if msg.Prefix == nil {
783 return fmt.Errorf("expected a prefix")
784 }
[34]785
[43]786 var channels string
787 if err := parseMessageParams(msg, &channels); err != nil {
788 return err
[34]789 }
790
[43]791 for _, ch := range strings.Split(channels, ",") {
[55]792 if msg.Prefix.Name == uc.nick {
793 uc.logger.Printf("parted channel %q", ch)
[435]794 if uch, ok := uc.channels[ch]; ok {
795 delete(uc.channels, ch)
796 uch.updateAutoDetach(0)
797 }
[34]798 } else {
[55]799 ch, err := uc.getChannel(ch)
[34]800 if err != nil {
801 return err
802 }
803 delete(ch.Members, msg.Prefix.Name)
804 }
[69]805
[245]806 chMsg := msg.Copy()
807 chMsg.Params[0] = ch
808 uc.produce(ch, chMsg, nil)
[34]809 }
[159]810 case "KICK":
811 if msg.Prefix == nil {
812 return fmt.Errorf("expected a prefix")
813 }
814
815 var channel, user string
816 if err := parseMessageParams(msg, &channel, &user); err != nil {
817 return err
818 }
819
820 if user == uc.nick {
821 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
822 delete(uc.channels, channel)
823 } else {
824 ch, err := uc.getChannel(channel)
825 if err != nil {
826 return err
827 }
828 delete(ch.Members, user)
829 }
830
[245]831 uc.produce(channel, msg, nil)
[83]832 case "QUIT":
833 if msg.Prefix == nil {
834 return fmt.Errorf("expected a prefix")
835 }
836
837 if msg.Prefix.Name == uc.nick {
838 uc.logger.Printf("quit")
839 }
840
841 for _, ch := range uc.channels {
[178]842 if _, ok := ch.Members[msg.Prefix.Name]; ok {
843 delete(ch.Members, msg.Prefix.Name)
844
[215]845 uc.appendLog(ch.Name, msg)
[178]846 }
[83]847 }
848
849 if msg.Prefix.Name != uc.nick {
850 uc.forEachDownstream(func(dc *downstreamConn) {
[261]851 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[83]852 })
853 }
[19]854 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
[43]855 var name, topic string
856 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
857 return err
[19]858 }
[55]859 ch, err := uc.getChannel(name)
[19]860 if err != nil {
861 return err
862 }
863 if msg.Command == irc.RPL_TOPIC {
[43]864 ch.Topic = topic
[19]865 } else {
866 ch.Topic = ""
867 }
868 case "TOPIC":
[405]869 if msg.Prefix == nil {
870 return fmt.Errorf("expected a prefix")
871 }
872
[43]873 var name string
[74]874 if err := parseMessageParams(msg, &name); err != nil {
[43]875 return err
[19]876 }
[55]877 ch, err := uc.getChannel(name)
[19]878 if err != nil {
879 return err
880 }
881 if len(msg.Params) > 1 {
882 ch.Topic = msg.Params[1]
[405]883 ch.TopicWho = msg.Prefix.Copy()
884 ch.TopicTime = time.Now() // TODO use msg.Tags["time"]
[19]885 } else {
886 ch.Topic = ""
887 }
[245]888 uc.produce(ch.Name, msg, nil)
[139]889 case "MODE":
890 var name, modeStr string
891 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
892 return err
893 }
894
895 if !uc.isChannel(name) { // user mode change
896 if name != uc.nick {
897 return fmt.Errorf("received MODE message for unknown nick %q", name)
898 }
899 return uc.modes.Apply(modeStr)
900 // TODO: notify downstreams about user mode change?
901 } else { // channel mode change
902 ch, err := uc.getChannel(name)
903 if err != nil {
904 return err
905 }
906
[293]907 needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
908 if err != nil {
909 return err
[139]910 }
911
[293]912 uc.appendLog(ch.Name, msg)
913
[338]914 if ch, ok := uc.network.channels[name]; !ok || !ch.Detached {
915 uc.forEachDownstream(func(dc *downstreamConn) {
916 params := make([]string, len(msg.Params))
917 params[0] = dc.marshalEntity(uc.network, name)
918 params[1] = modeStr
919
920 copy(params[2:], msg.Params[2:])
921 for i, modeParam := range params[2:] {
922 if _, ok := needMarshaling[i]; ok {
923 params[2+i] = dc.marshalEntity(uc.network, modeParam)
924 }
[293]925 }
926
[338]927 dc.SendMessage(&irc.Message{
928 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
929 Command: "MODE",
930 Params: params,
931 })
[293]932 })
[338]933 }
[139]934 }
935 case irc.RPL_UMODEIS:
936 if err := parseMessageParams(msg, nil); err != nil {
937 return err
938 }
939 modeStr := ""
940 if len(msg.Params) > 1 {
941 modeStr = msg.Params[1]
942 }
943
944 uc.modes = ""
945 if err := uc.modes.Apply(modeStr); err != nil {
946 return err
947 }
948 // TODO: send RPL_UMODEIS to downstream connections when applicable
949 case irc.RPL_CHANNELMODEIS:
950 var channel string
951 if err := parseMessageParams(msg, nil, &channel); err != nil {
952 return err
953 }
954 modeStr := ""
955 if len(msg.Params) > 2 {
956 modeStr = msg.Params[2]
957 }
958
959 ch, err := uc.getChannel(channel)
960 if err != nil {
961 return err
962 }
963
964 firstMode := ch.modes == nil
965 ch.modes = make(map[byte]string)
[293]966 if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
[139]967 return err
968 }
969 if firstMode {
[338]970 if c, ok := uc.network.channels[channel]; !ok || !c.Detached {
971 modeStr, modeParams := ch.modes.Format()
[139]972
[338]973 uc.forEachDownstream(func(dc *downstreamConn) {
974 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
975 params = append(params, modeParams...)
[139]976
[338]977 dc.SendMessage(&irc.Message{
978 Prefix: dc.srv.prefix(),
979 Command: irc.RPL_CHANNELMODEIS,
980 Params: params,
981 })
[139]982 })
[338]983 }
[139]984 }
[162]985 case rpl_creationtime:
986 var channel, creationTime string
987 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
988 return err
989 }
990
991 ch, err := uc.getChannel(channel)
992 if err != nil {
993 return err
994 }
995
996 firstCreationTime := ch.creationTime == ""
997 ch.creationTime = creationTime
998 if firstCreationTime {
999 uc.forEachDownstream(func(dc *downstreamConn) {
1000 dc.SendMessage(&irc.Message{
1001 Prefix: dc.srv.prefix(),
1002 Command: rpl_creationtime,
[403]1003 Params: []string{dc.nick, dc.marshalEntity(uc.network, ch.Name), creationTime},
[162]1004 })
1005 })
1006 }
[19]1007 case rpl_topicwhotime:
[43]1008 var name, who, timeStr string
1009 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
1010 return err
[19]1011 }
[55]1012 ch, err := uc.getChannel(name)
[19]1013 if err != nil {
1014 return err
1015 }
[405]1016 firstTopicWhoTime := ch.TopicWho == nil
1017 ch.TopicWho = irc.ParsePrefix(who)
[43]1018 sec, err := strconv.ParseInt(timeStr, 10, 64)
[19]1019 if err != nil {
1020 return fmt.Errorf("failed to parse topic time: %v", err)
1021 }
1022 ch.TopicTime = time.Unix(sec, 0)
[405]1023 if firstTopicWhoTime {
1024 uc.forEachDownstream(func(dc *downstreamConn) {
1025 topicWho := dc.marshalUserPrefix(uc.network, ch.TopicWho)
1026 dc.SendMessage(&irc.Message{
1027 Prefix: dc.srv.prefix(),
1028 Command: rpl_topicwhotime,
1029 Params: []string{
1030 dc.nick,
1031 dc.marshalEntity(uc.network, ch.Name),
1032 topicWho.String(),
1033 timeStr,
1034 },
1035 })
1036 })
1037 }
[177]1038 case irc.RPL_LIST:
1039 var channel, clients, topic string
1040 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
1041 return err
1042 }
1043
[181]1044 pl := uc.getPendingLIST()
[177]1045 if pl == nil {
1046 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
1047 }
1048
1049 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
1050 dc.SendMessage(&irc.Message{
1051 Prefix: dc.srv.prefix(),
1052 Command: irc.RPL_LIST,
[260]1053 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
[177]1054 })
1055 })
1056 case irc.RPL_LISTEND:
[181]1057 ok := uc.endPendingLISTs(false)
[177]1058 if !ok {
1059 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
1060 }
[19]1061 case irc.RPL_NAMREPLY:
[43]1062 var name, statusStr, members string
1063 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
1064 return err
[19]1065 }
[140]1066
1067 ch, ok := uc.channels[name]
1068 if !ok {
1069 // NAMES on a channel we have not joined, forward to downstream
[161]1070 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1071 channel := dc.marshalEntity(uc.network, name)
[174]1072 members := splitSpace(members)
[140]1073 for i, member := range members {
[292]1074 memberships, nick := uc.parseMembershipPrefix(member)
1075 members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
[140]1076 }
1077 memberStr := strings.Join(members, " ")
1078
1079 dc.SendMessage(&irc.Message{
1080 Prefix: dc.srv.prefix(),
1081 Command: irc.RPL_NAMREPLY,
1082 Params: []string{dc.nick, statusStr, channel, memberStr},
1083 })
1084 })
1085 return nil
[19]1086 }
1087
[43]1088 status, err := parseChannelStatus(statusStr)
[19]1089 if err != nil {
1090 return err
1091 }
1092 ch.Status = status
1093
[174]1094 for _, s := range splitSpace(members) {
[292]1095 memberships, nick := uc.parseMembershipPrefix(s)
1096 ch.Members[nick] = memberships
[19]1097 }
1098 case irc.RPL_ENDOFNAMES:
[43]1099 var name string
1100 if err := parseMessageParams(msg, nil, &name); err != nil {
1101 return err
[25]1102 }
[140]1103
1104 ch, ok := uc.channels[name]
1105 if !ok {
1106 // NAMES on a channel we have not joined, forward to downstream
[161]1107 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1108 channel := dc.marshalEntity(uc.network, name)
[140]1109
1110 dc.SendMessage(&irc.Message{
1111 Prefix: dc.srv.prefix(),
1112 Command: irc.RPL_ENDOFNAMES,
1113 Params: []string{dc.nick, channel, "End of /NAMES list"},
1114 })
1115 })
1116 return nil
[25]1117 }
1118
[34]1119 if ch.complete {
1120 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
1121 }
[25]1122 ch.complete = true
[27]1123
[338]1124 if c, ok := uc.network.channels[name]; !ok || !c.Detached {
1125 uc.forEachDownstream(func(dc *downstreamConn) {
1126 forwardChannel(dc, ch)
1127 })
1128 }
[127]1129 case irc.RPL_WHOREPLY:
1130 var channel, username, host, server, nick, mode, trailing string
1131 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &mode, &trailing); err != nil {
1132 return err
1133 }
1134
1135 parts := strings.SplitN(trailing, " ", 2)
1136 if len(parts) != 2 {
1137 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong trailing parameter: %s", trailing)
1138 }
1139 realname := parts[1]
1140 hops, err := strconv.Atoi(parts[0])
1141 if err != nil {
1142 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong hop count: %s", parts[0])
1143 }
1144 hops++
1145
1146 trailing = strconv.Itoa(hops) + " " + realname
1147
[161]1148 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[127]1149 channel := channel
1150 if channel != "*" {
[260]1151 channel = dc.marshalEntity(uc.network, channel)
[127]1152 }
[260]1153 nick := dc.marshalEntity(uc.network, nick)
[127]1154 dc.SendMessage(&irc.Message{
1155 Prefix: dc.srv.prefix(),
1156 Command: irc.RPL_WHOREPLY,
1157 Params: []string{dc.nick, channel, username, host, server, nick, mode, trailing},
1158 })
1159 })
1160 case irc.RPL_ENDOFWHO:
1161 var name string
1162 if err := parseMessageParams(msg, nil, &name); err != nil {
1163 return err
1164 }
1165
[161]1166 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[127]1167 name := name
1168 if name != "*" {
1169 // TODO: support WHO masks
[260]1170 name = dc.marshalEntity(uc.network, name)
[127]1171 }
1172 dc.SendMessage(&irc.Message{
1173 Prefix: dc.srv.prefix(),
1174 Command: irc.RPL_ENDOFWHO,
[142]1175 Params: []string{dc.nick, name, "End of /WHO list"},
[127]1176 })
1177 })
[128]1178 case irc.RPL_WHOISUSER:
1179 var nick, username, host, realname string
1180 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
1181 return err
1182 }
1183
[161]1184 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1185 nick := dc.marshalEntity(uc.network, nick)
[128]1186 dc.SendMessage(&irc.Message{
1187 Prefix: dc.srv.prefix(),
1188 Command: irc.RPL_WHOISUSER,
1189 Params: []string{dc.nick, nick, username, host, "*", realname},
1190 })
1191 })
1192 case irc.RPL_WHOISSERVER:
1193 var nick, server, serverInfo string
1194 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
1195 return err
1196 }
1197
[161]1198 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1199 nick := dc.marshalEntity(uc.network, nick)
[128]1200 dc.SendMessage(&irc.Message{
1201 Prefix: dc.srv.prefix(),
1202 Command: irc.RPL_WHOISSERVER,
1203 Params: []string{dc.nick, nick, server, serverInfo},
1204 })
1205 })
1206 case irc.RPL_WHOISOPERATOR:
1207 var nick string
1208 if err := parseMessageParams(msg, nil, &nick); err != nil {
1209 return err
1210 }
1211
[161]1212 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1213 nick := dc.marshalEntity(uc.network, nick)
[128]1214 dc.SendMessage(&irc.Message{
1215 Prefix: dc.srv.prefix(),
1216 Command: irc.RPL_WHOISOPERATOR,
1217 Params: []string{dc.nick, nick, "is an IRC operator"},
1218 })
1219 })
1220 case irc.RPL_WHOISIDLE:
1221 var nick string
1222 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
1223 return err
1224 }
1225
[161]1226 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1227 nick := dc.marshalEntity(uc.network, nick)
[128]1228 params := []string{dc.nick, nick}
1229 params = append(params, msg.Params[2:]...)
1230 dc.SendMessage(&irc.Message{
1231 Prefix: dc.srv.prefix(),
1232 Command: irc.RPL_WHOISIDLE,
1233 Params: params,
1234 })
1235 })
1236 case irc.RPL_WHOISCHANNELS:
1237 var nick, channelList string
1238 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1239 return err
1240 }
[174]1241 channels := splitSpace(channelList)
[128]1242
[161]1243 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1244 nick := dc.marshalEntity(uc.network, nick)
[128]1245 channelList := make([]string, len(channels))
1246 for i, channel := range channels {
[139]1247 prefix, channel := uc.parseMembershipPrefix(channel)
[260]1248 channel = dc.marshalEntity(uc.network, channel)
[292]1249 channelList[i] = prefix.Format(dc) + channel
[128]1250 }
1251 channels := strings.Join(channelList, " ")
1252 dc.SendMessage(&irc.Message{
1253 Prefix: dc.srv.prefix(),
1254 Command: irc.RPL_WHOISCHANNELS,
1255 Params: []string{dc.nick, nick, channels},
1256 })
1257 })
1258 case irc.RPL_ENDOFWHOIS:
1259 var nick string
1260 if err := parseMessageParams(msg, nil, &nick); err != nil {
1261 return err
1262 }
1263
[161]1264 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1265 nick := dc.marshalEntity(uc.network, nick)
[128]1266 dc.SendMessage(&irc.Message{
1267 Prefix: dc.srv.prefix(),
1268 Command: irc.RPL_ENDOFWHOIS,
[142]1269 Params: []string{dc.nick, nick, "End of /WHOIS list"},
[128]1270 })
1271 })
[115]1272 case "INVITE":
[273]1273 var nick, channel string
[115]1274 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1275 return err
1276 }
1277
1278 uc.forEachDownstream(func(dc *downstreamConn) {
1279 dc.SendMessage(&irc.Message{
[260]1280 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
[115]1281 Command: "INVITE",
[260]1282 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
[115]1283 })
1284 })
[163]1285 case irc.RPL_INVITING:
[273]1286 var nick, channel string
[304]1287 if err := parseMessageParams(msg, nil, &nick, &channel); err != nil {
[163]1288 return err
1289 }
1290
1291 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1292 dc.SendMessage(&irc.Message{
1293 Prefix: dc.srv.prefix(),
1294 Command: irc.RPL_INVITING,
[260]1295 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
[163]1296 })
1297 })
[272]1298 case irc.RPL_AWAY:
1299 var nick, reason string
1300 if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
1301 return err
1302 }
1303
[274]1304 uc.forEachDownstream(func(dc *downstreamConn) {
[272]1305 dc.SendMessage(&irc.Message{
1306 Prefix: dc.srv.prefix(),
1307 Command: irc.RPL_AWAY,
1308 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), reason},
1309 })
1310 })
[276]1311 case "AWAY":
1312 if msg.Prefix == nil {
1313 return fmt.Errorf("expected a prefix")
1314 }
1315
1316 uc.forEachDownstream(func(dc *downstreamConn) {
1317 if !dc.caps["away-notify"] {
1318 return
1319 }
1320 dc.SendMessage(&irc.Message{
1321 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1322 Command: "AWAY",
1323 Params: msg.Params,
1324 })
1325 })
[300]1326 case irc.RPL_BANLIST, irc.RPL_INVITELIST, irc.RPL_EXCEPTLIST:
1327 var channel, mask string
1328 if err := parseMessageParams(msg, nil, &channel, &mask); err != nil {
1329 return err
1330 }
1331 var addNick, addTime string
1332 if len(msg.Params) >= 5 {
1333 addNick = msg.Params[3]
1334 addTime = msg.Params[4]
1335 }
1336
1337 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1338 channel := dc.marshalEntity(uc.network, channel)
1339
1340 var params []string
1341 if addNick != "" && addTime != "" {
1342 addNick := dc.marshalEntity(uc.network, addNick)
1343 params = []string{dc.nick, channel, mask, addNick, addTime}
1344 } else {
1345 params = []string{dc.nick, channel, mask}
1346 }
1347
1348 dc.SendMessage(&irc.Message{
1349 Prefix: dc.srv.prefix(),
1350 Command: msg.Command,
1351 Params: params,
1352 })
1353 })
1354 case irc.RPL_ENDOFBANLIST, irc.RPL_ENDOFINVITELIST, irc.RPL_ENDOFEXCEPTLIST:
1355 var channel, trailing string
1356 if err := parseMessageParams(msg, nil, &channel, &trailing); err != nil {
1357 return err
1358 }
1359
1360 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1361 upstreamChannel := dc.marshalEntity(uc.network, channel)
1362 dc.SendMessage(&irc.Message{
1363 Prefix: dc.srv.prefix(),
1364 Command: msg.Command,
1365 Params: []string{dc.nick, upstreamChannel, trailing},
1366 })
1367 })
[302]1368 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1369 var command, reason string
1370 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1371 return err
1372 }
1373
1374 if command == "LIST" {
1375 ok := uc.endPendingLISTs(false)
1376 if !ok {
1377 return fmt.Errorf("unexpected response for LIST: %q: no matching pending LIST", msg.Command)
1378 }
1379 }
1380
[355]1381 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1382 dc.SendMessage(&irc.Message{
1383 Prefix: uc.srv.prefix(),
1384 Command: msg.Command,
1385 Params: []string{dc.nick, command, reason},
[302]1386 })
[355]1387 })
[155]1388 case "ACK":
1389 // Ignore
[198]1390 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1391 // Ignore
[16]1392 case irc.RPL_YOURHOST, irc.RPL_CREATED:
[14]1393 // Ignore
1394 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
1395 // Ignore
1396 case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
1397 // Ignore
[177]1398 case irc.RPL_LISTSTART:
1399 // Ignore
[14]1400 case rpl_localusers, rpl_globalusers:
1401 // Ignore
[96]1402 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
[14]1403 // Ignore
[390]1404 case "ERROR":
1405 var text string
1406 if err := parseMessageParams(msg, &text); err != nil {
1407 return err
1408 }
1409 return fmt.Errorf("fatal server error: %v", text)
[389]1410 case irc.ERR_PASSWDMISMATCH, irc.ERR_ERRONEUSNICKNAME, irc.ERR_NICKNAMEINUSE, irc.ERR_NICKCOLLISION, irc.ERR_UNAVAILRESOURCE, irc.ERR_NOPERMFORHOST, irc.ERR_YOUREBANNEDCREEP:
[342]1411 if !uc.registered {
[399]1412 text := msg.Params[len(msg.Params)-1]
1413 return registrationError(text)
[342]1414 }
1415 fallthrough
[13]1416 default:
[95]1417 uc.logger.Printf("unhandled message: %v", msg)
[355]1418
1419 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1420 // best effort marshaling for unknown messages, replies and errors:
1421 // most numerics start with the user nick, marshal it if that's the case
1422 // otherwise, conservately keep the params without marshaling
1423 params := msg.Params
1424 if _, err := strconv.Atoi(msg.Command); err == nil { // numeric
1425 if len(msg.Params) > 0 && isOurNick(uc.network, msg.Params[0]) {
1426 params[0] = dc.nick
[302]1427 }
[355]1428 }
1429 dc.SendMessage(&irc.Message{
1430 Prefix: uc.srv.prefix(),
1431 Command: msg.Command,
1432 Params: params,
[302]1433 })
[355]1434 })
[13]1435 }
[14]1436 return nil
[13]1437}
1438
[435]1439func (uc *upstreamConn) handleDetachedMessage(sender string, text string, ch *Channel) {
1440 highlight := sender != uc.nick && isHighlight(text, uc.nick)
1441 if ch.RelayDetached == FilterMessage || ((ch.RelayDetached == FilterHighlight || ch.RelayDetached == FilterDefault) && highlight) {
1442 uc.forEachDownstream(func(dc *downstreamConn) {
1443 if highlight {
1444 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), sender, text))
1445 } else {
1446 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), sender, text))
1447 }
1448 })
1449 }
1450 if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && highlight) {
1451 uc.network.attach(ch)
1452 if err := uc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
1453 uc.logger.Printf("failed to update channel %q: %v", ch.Name, err)
1454 }
1455 }
1456}
1457
[281]1458func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
1459 caps := strings.Fields(capsStr)
1460 for _, s := range caps {
1461 kv := strings.SplitN(s, "=", 2)
1462 k := strings.ToLower(kv[0])
1463 var v string
1464 if len(kv) == 2 {
1465 v = kv[1]
1466 }
1467 uc.supportedCaps[k] = v
1468 }
1469}
1470
1471func (uc *upstreamConn) requestCaps() {
1472 var requestCaps []string
[282]1473 for c := range permanentUpstreamCaps {
[281]1474 if _, ok := uc.supportedCaps[c]; ok && !uc.caps[c] {
1475 requestCaps = append(requestCaps, c)
1476 }
1477 }
1478
1479 if uc.requestSASL() && !uc.caps["sasl"] {
1480 requestCaps = append(requestCaps, "sasl")
1481 }
1482
[282]1483 if len(requestCaps) == 0 {
1484 return
1485 }
1486
1487 uc.SendMessage(&irc.Message{
1488 Command: "CAP",
1489 Params: []string{"REQ", strings.Join(requestCaps, " ")},
1490 })
1491}
1492
1493func (uc *upstreamConn) requestSASL() bool {
1494 if uc.network.SASL.Mechanism == "" {
1495 return false
1496 }
1497
1498 v, ok := uc.supportedCaps["sasl"]
1499 if !ok {
1500 return false
1501 }
1502 if v != "" {
1503 mechanisms := strings.Split(v, ",")
1504 found := false
1505 for _, mech := range mechanisms {
1506 if strings.EqualFold(mech, uc.network.SASL.Mechanism) {
1507 found = true
1508 break
1509 }
1510 }
1511 if !found {
1512 return false
1513 }
1514 }
1515
1516 return true
1517}
1518
1519func (uc *upstreamConn) handleCapAck(name string, ok bool) error {
1520 uc.caps[name] = ok
1521
1522 switch name {
1523 case "sasl":
1524 if !ok {
1525 uc.logger.Printf("server refused to acknowledge the SASL capability")
1526 return nil
1527 }
1528
1529 auth := &uc.network.SASL
1530 switch auth.Mechanism {
1531 case "PLAIN":
1532 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1533 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
[307]1534 case "EXTERNAL":
1535 uc.logger.Printf("starting SASL EXTERNAL authentication")
1536 uc.saslClient = sasl.NewExternalClient("")
[282]1537 default:
1538 return fmt.Errorf("unsupported SASL mechanism %q", name)
1539 }
1540
[281]1541 uc.SendMessage(&irc.Message{
[282]1542 Command: "AUTHENTICATE",
1543 Params: []string{auth.Mechanism},
[281]1544 })
[282]1545 default:
1546 if permanentUpstreamCaps[name] {
1547 break
1548 }
1549 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
[281]1550 }
[282]1551 return nil
[281]1552}
1553
[174]1554func splitSpace(s string) []string {
1555 return strings.FieldsFunc(s, func(r rune) bool {
1556 return r == ' '
1557 })
1558}
1559
[55]1560func (uc *upstreamConn) register() {
[77]1561 uc.nick = uc.network.Nick
1562 uc.username = uc.network.Username
1563 if uc.username == "" {
1564 uc.username = uc.nick
1565 }
1566 uc.realname = uc.network.Realname
1567 if uc.realname == "" {
1568 uc.realname = uc.nick
1569 }
1570
[60]1571 uc.SendMessage(&irc.Message{
[92]1572 Command: "CAP",
1573 Params: []string{"LS", "302"},
1574 })
1575
[93]1576 if uc.network.Pass != "" {
1577 uc.SendMessage(&irc.Message{
1578 Command: "PASS",
1579 Params: []string{uc.network.Pass},
1580 })
1581 }
1582
[92]1583 uc.SendMessage(&irc.Message{
[13]1584 Command: "NICK",
[69]1585 Params: []string{uc.nick},
[60]1586 })
1587 uc.SendMessage(&irc.Message{
[13]1588 Command: "USER",
[77]1589 Params: []string{uc.username, "0", "*", uc.realname},
[60]1590 })
[44]1591}
[13]1592
[197]1593func (uc *upstreamConn) runUntilRegistered() error {
1594 for !uc.registered {
[212]1595 msg, err := uc.ReadMessage()
[197]1596 if err != nil {
1597 return fmt.Errorf("failed to read message: %v", err)
1598 }
1599
1600 if err := uc.handleMessage(msg); err != nil {
[399]1601 if _, ok := err.(registrationError); ok {
1602 return err
1603 } else {
1604 msg.Tags = nil // prevent message tags from cluttering logs
1605 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1606 }
[197]1607 }
1608 }
1609
[263]1610 for _, command := range uc.network.ConnectCommands {
1611 m, err := irc.ParseMessage(command)
1612 if err != nil {
1613 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1614 } else {
1615 uc.SendMessage(m)
1616 }
1617 }
1618
[197]1619 return nil
1620}
1621
[165]1622func (uc *upstreamConn) readMessages(ch chan<- event) error {
[13]1623 for {
[210]1624 msg, err := uc.ReadMessage()
[13]1625 if err == io.EOF {
1626 break
1627 } else if err != nil {
1628 return fmt.Errorf("failed to read IRC command: %v", err)
1629 }
1630
[165]1631 ch <- eventUpstreamMessage{msg, uc}
[13]1632 }
1633
[45]1634 return nil
[13]1635}
[60]1636
[303]1637func (uc *upstreamConn) SendMessage(msg *irc.Message) {
1638 if !uc.caps["message-tags"] {
1639 msg = msg.Copy()
1640 msg.Tags = nil
1641 }
1642
1643 uc.conn.SendMessage(msg)
1644}
1645
[176]1646func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
[278]1647 if uc.caps["labeled-response"] {
[155]1648 if msg.Tags == nil {
1649 msg.Tags = make(map[string]irc.TagValue)
1650 }
[176]1651 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
[161]1652 uc.nextLabelID++
[155]1653 }
1654 uc.SendMessage(msg)
1655}
[178]1656
[428]1657// appendLog appends a message to the log file.
1658//
1659// The internal message ID is returned. If the message isn't recorded in the
1660// log file, an empty string is returned.
1661func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string) {
[423]1662 if uc.user.msgStore == nil {
[428]1663 return ""
[178]1664 }
[215]1665
[284]1666 detached := false
1667 if ch, ok := uc.network.channels[entity]; ok {
1668 detached = ch.Detached
1669 }
1670
[253]1671 history, ok := uc.network.history[entity]
1672 if !ok {
[423]1673 lastID, err := uc.user.msgStore.LastMsgID(uc.network, entity, time.Now())
[409]1674 if err != nil {
1675 uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
[428]1676 return ""
[409]1677 }
1678
[253]1679 history = &networkHistory{
[409]1680 clients: make(map[string]string),
[253]1681 }
1682 uc.network.history[entity] = history
1683
1684 for clientName, _ := range uc.network.offlineClients {
[409]1685 history.clients[clientName] = lastID
[253]1686 }
[284]1687
1688 if detached {
1689 // If the channel is detached, online clients act as offline
1690 // clients too
1691 uc.forEachDownstream(func(dc *downstreamConn) {
[409]1692 history.clients[dc.clientName] = lastID
[284]1693 })
1694 }
[253]1695 }
1696
[423]1697 msgID, err := uc.user.msgStore.Append(uc.network, entity, msg)
[409]1698 if err != nil {
1699 uc.logger.Printf("failed to log message: %v", err)
[428]1700 return ""
[409]1701 }
[406]1702
[428]1703 return msgID
[253]1704}
1705
[409]1706// produce appends a message to the logs and forwards it to connected downstream
1707// connections.
[245]1708//
1709// If origin is not nil and origin doesn't support echo-message, the message is
1710// forwarded to all connections except origin.
[239]1711func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
[428]1712 var msgID string
[239]1713 if target != "" {
[428]1714 msgID = uc.appendLog(target, msg)
[239]1715 }
1716
[284]1717 // Don't forward messages if it's a detached channel
1718 if ch, ok := uc.network.channels[target]; ok && ch.Detached {
1719 return
1720 }
1721
[227]1722 uc.forEachDownstream(func(dc *downstreamConn) {
[238]1723 if dc != origin || dc.caps["echo-message"] {
[428]1724 dc.sendMessageWithID(dc.marshalMessage(msg, uc.network), msgID)
1725 } else {
1726 dc.advanceMessageWithID(msg, msgID)
[238]1727 }
[227]1728 })
[226]1729}
1730
[198]1731func (uc *upstreamConn) updateAway() {
1732 away := true
1733 uc.forEachDownstream(func(*downstreamConn) {
1734 away = false
1735 })
1736 if away == uc.away {
1737 return
1738 }
1739 if away {
1740 uc.SendMessage(&irc.Message{
1741 Command: "AWAY",
1742 Params: []string{"Auto away"},
1743 })
1744 } else {
1745 uc.SendMessage(&irc.Message{
1746 Command: "AWAY",
1747 })
1748 }
1749 uc.away = away
1750}
[435]1751
1752func (uc *upstreamConn) updateChannelAutoDetach(name string) {
1753 if uch, ok := uc.channels[name]; ok {
1754 if ch, ok := uc.network.channels[name]; ok && !ch.Detached {
1755 uch.updateAutoDetach(ch.DetachAfter)
1756 }
1757 }
1758}
Note: See TracBrowser for help on using the repository browser.