source: code/trunk/upstream.go@ 460

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

Maintain state for upstream ISUPPORT

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