source: code/trunk/upstream.go@ 267

Last change on this file since 267 was 267, checked in by contact, 5 years ago

Add network.channels, remove DB.GetChannel

Store the list of configured channels in the network data structure.
This removes the need for a database lookup and will be useful for
detached channels.

File size: 33.6 KB
Line 
1package soju
2
3import (
4 "crypto/tls"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io"
9 "net"
10 "strconv"
11 "strings"
12 "time"
13
14 "github.com/emersion/go-sasl"
15 "gopkg.in/irc.v3"
16)
17
18type upstreamChannel struct {
19 Name string
20 conn *upstreamConn
21 Topic string
22 TopicWho string
23 TopicTime time.Time
24 Status channelStatus
25 modes channelModes
26 creationTime string
27 Members map[string]*membership
28 complete bool
29}
30
31type upstreamConn struct {
32 conn
33
34 network *network
35 user *user
36
37 serverName string
38 availableUserModes string
39 availableChannelModes map[byte]channelModeType
40 availableChannelTypes string
41 availableMemberships []membership
42
43 registered bool
44 nick string
45 username string
46 realname string
47 modes userModes
48 channels map[string]*upstreamChannel
49 caps map[string]string
50 batches map[string]batch
51 away bool
52
53 tagsSupported bool
54 labelsSupported bool
55 nextLabelID uint64
56
57 saslClient sasl.Client
58 saslStarted bool
59
60 // set of LIST commands in progress, per downstream
61 pendingLISTDownstreamSet map[uint64]struct{}
62
63 messageLoggers map[string]*messageLogger
64}
65
66func connectToUpstream(network *network) (*upstreamConn, error) {
67 logger := &prefixLogger{network.user.srv.Logger, fmt.Sprintf("upstream %q: ", network.Addr)}
68
69 addr := network.Addr
70 if !strings.ContainsRune(addr, ':') {
71 addr = addr + ":6697"
72 }
73
74 dialer := net.Dialer{Timeout: connectTimeout}
75
76 logger.Printf("connecting to TLS server at address %q", addr)
77 netConn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
78 if err != nil {
79 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
80 }
81
82 uc := &upstreamConn{
83 conn: *newConn(network.user.srv, netConn, logger),
84 network: network,
85 user: network.user,
86 channels: make(map[string]*upstreamChannel),
87 caps: make(map[string]string),
88 batches: make(map[string]batch),
89 availableChannelTypes: stdChannelTypes,
90 availableChannelModes: stdChannelModes,
91 availableMemberships: stdMemberships,
92 pendingLISTDownstreamSet: make(map[uint64]struct{}),
93 messageLoggers: make(map[string]*messageLogger),
94 }
95
96 return uc, nil
97}
98
99func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) {
100 uc.network.forEachDownstream(f)
101}
102
103func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) {
104 uc.forEachDownstream(func(dc *downstreamConn) {
105 if id != 0 && id != dc.id {
106 return
107 }
108 f(dc)
109 })
110}
111
112func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
113 ch, ok := uc.channels[name]
114 if !ok {
115 return nil, fmt.Errorf("unknown channel %q", name)
116 }
117 return ch, nil
118}
119
120func (uc *upstreamConn) isChannel(entity string) bool {
121 if i := strings.IndexByte(uc.availableChannelTypes, entity[0]); i >= 0 {
122 return true
123 }
124 return false
125}
126
127func (uc *upstreamConn) getPendingLIST() *pendingLIST {
128 for _, pl := range uc.user.pendingLISTs {
129 if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
130 continue
131 }
132 return &pl
133 }
134 return nil
135}
136
137func (uc *upstreamConn) endPendingLISTs(all bool) (found bool) {
138 found = false
139 for i := 0; i < len(uc.user.pendingLISTs); i++ {
140 pl := uc.user.pendingLISTs[i]
141 if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
142 continue
143 }
144 delete(pl.pendingCommands, uc.network.ID)
145 if len(pl.pendingCommands) == 0 {
146 uc.user.pendingLISTs = append(uc.user.pendingLISTs[:i], uc.user.pendingLISTs[i+1:]...)
147 i--
148 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
149 dc.SendMessage(&irc.Message{
150 Prefix: dc.srv.prefix(),
151 Command: irc.RPL_LISTEND,
152 Params: []string{dc.nick, "End of /LIST"},
153 })
154 })
155 }
156 found = true
157 if !all {
158 delete(uc.pendingLISTDownstreamSet, pl.downstreamID)
159 uc.user.forEachUpstream(func(uc *upstreamConn) {
160 uc.trySendLIST(pl.downstreamID)
161 })
162 return
163 }
164 }
165 return
166}
167
168func (uc *upstreamConn) trySendLIST(downstreamID uint64) {
169 if _, ok := uc.pendingLISTDownstreamSet[downstreamID]; ok {
170 // a LIST command is already pending
171 // we will try again when that command is completed
172 return
173 }
174
175 for _, pl := range uc.user.pendingLISTs {
176 if pl.downstreamID != downstreamID {
177 continue
178 }
179 // this is the first pending LIST command list of the downstream
180 listCommand, ok := pl.pendingCommands[uc.network.ID]
181 if !ok {
182 // there is no command for this upstream in these LIST commands
183 // do not send anything
184 continue
185 }
186 // there is a command for this upstream in these LIST commands
187 // send it now
188
189 uc.SendMessageLabeled(downstreamID, listCommand)
190
191 uc.pendingLISTDownstreamSet[downstreamID] = struct{}{}
192 return
193 }
194}
195
196func (uc *upstreamConn) parseMembershipPrefix(s string) (membership *membership, nick string) {
197 for _, m := range uc.availableMemberships {
198 if m.Prefix == s[0] {
199 return &m, s[1:]
200 }
201 }
202 return nil, s
203}
204
205func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
206 var label string
207 if l, ok := msg.GetTag("label"); ok {
208 label = l
209 }
210
211 var msgBatch *batch
212 if batchName, ok := msg.GetTag("batch"); ok {
213 b, ok := uc.batches[batchName]
214 if !ok {
215 return fmt.Errorf("unexpected batch reference: batch was not defined: %q", batchName)
216 }
217 msgBatch = &b
218 if label == "" {
219 label = msgBatch.Label
220 }
221 }
222
223 var downstreamID uint64 = 0
224 if label != "" {
225 var labelOffset uint64
226 n, err := fmt.Sscanf(label, "sd-%d-%d", &downstreamID, &labelOffset)
227 if err == nil && n < 2 {
228 err = errors.New("not enough arguments")
229 }
230 if err != nil {
231 return fmt.Errorf("unexpected message label: invalid downstream reference for label %q: %v", label, err)
232 }
233 }
234
235 if _, ok := msg.Tags["time"]; !ok {
236 msg.Tags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
237 }
238
239 switch msg.Command {
240 case "PING":
241 uc.SendMessage(&irc.Message{
242 Command: "PONG",
243 Params: msg.Params,
244 })
245 return nil
246 case "NOTICE":
247 if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
248 uc.produce("", msg, nil)
249 } else { // regular user NOTICE
250 var entity, text string
251 if err := parseMessageParams(msg, &entity, &text); err != nil {
252 return err
253 }
254
255 target := entity
256 if target == uc.nick {
257 target = msg.Prefix.Name
258 }
259 uc.produce(target, msg, nil)
260 }
261 case "CAP":
262 var subCmd string
263 if err := parseMessageParams(msg, nil, &subCmd); err != nil {
264 return err
265 }
266 subCmd = strings.ToUpper(subCmd)
267 subParams := msg.Params[2:]
268 switch subCmd {
269 case "LS":
270 if len(subParams) < 1 {
271 return newNeedMoreParamsError(msg.Command)
272 }
273 caps := strings.Fields(subParams[len(subParams)-1])
274 more := len(subParams) >= 2 && msg.Params[len(subParams)-2] == "*"
275
276 for _, s := range caps {
277 kv := strings.SplitN(s, "=", 2)
278 k := strings.ToLower(kv[0])
279 var v string
280 if len(kv) == 2 {
281 v = kv[1]
282 }
283 uc.caps[k] = v
284 }
285
286 if more {
287 break // wait to receive all capabilities
288 }
289
290 requestCaps := make([]string, 0, 16)
291 for _, c := range []string{"message-tags", "batch", "labeled-response", "server-time"} {
292 if _, ok := uc.caps[c]; ok {
293 requestCaps = append(requestCaps, c)
294 }
295 }
296
297 if uc.requestSASL() {
298 requestCaps = append(requestCaps, "sasl")
299 }
300
301 if len(requestCaps) > 0 {
302 uc.SendMessage(&irc.Message{
303 Command: "CAP",
304 Params: []string{"REQ", strings.Join(requestCaps, " ")},
305 })
306 }
307
308 if uc.requestSASL() {
309 break // we'll send CAP END after authentication is completed
310 }
311
312 uc.SendMessage(&irc.Message{
313 Command: "CAP",
314 Params: []string{"END"},
315 })
316 case "ACK", "NAK":
317 if len(subParams) < 1 {
318 return newNeedMoreParamsError(msg.Command)
319 }
320 caps := strings.Fields(subParams[0])
321
322 for _, name := range caps {
323 if err := uc.handleCapAck(strings.ToLower(name), subCmd == "ACK"); err != nil {
324 return err
325 }
326 }
327
328 if uc.saslClient == nil {
329 uc.SendMessage(&irc.Message{
330 Command: "CAP",
331 Params: []string{"END"},
332 })
333 }
334 default:
335 uc.logger.Printf("unhandled message: %v", msg)
336 }
337 case "AUTHENTICATE":
338 if uc.saslClient == nil {
339 return fmt.Errorf("received unexpected AUTHENTICATE message")
340 }
341
342 // TODO: if a challenge is 400 bytes long, buffer it
343 var challengeStr string
344 if err := parseMessageParams(msg, &challengeStr); err != nil {
345 uc.SendMessage(&irc.Message{
346 Command: "AUTHENTICATE",
347 Params: []string{"*"},
348 })
349 return err
350 }
351
352 var challenge []byte
353 if challengeStr != "+" {
354 var err error
355 challenge, err = base64.StdEncoding.DecodeString(challengeStr)
356 if err != nil {
357 uc.SendMessage(&irc.Message{
358 Command: "AUTHENTICATE",
359 Params: []string{"*"},
360 })
361 return err
362 }
363 }
364
365 var resp []byte
366 var err error
367 if !uc.saslStarted {
368 _, resp, err = uc.saslClient.Start()
369 uc.saslStarted = true
370 } else {
371 resp, err = uc.saslClient.Next(challenge)
372 }
373 if err != nil {
374 uc.SendMessage(&irc.Message{
375 Command: "AUTHENTICATE",
376 Params: []string{"*"},
377 })
378 return err
379 }
380
381 // TODO: send response in multiple chunks if >= 400 bytes
382 var respStr = "+"
383 if resp != nil {
384 respStr = base64.StdEncoding.EncodeToString(resp)
385 }
386
387 uc.SendMessage(&irc.Message{
388 Command: "AUTHENTICATE",
389 Params: []string{respStr},
390 })
391 case irc.RPL_LOGGEDIN:
392 var account string
393 if err := parseMessageParams(msg, nil, nil, &account); err != nil {
394 return err
395 }
396 uc.logger.Printf("logged in with account %q", account)
397 case irc.RPL_LOGGEDOUT:
398 uc.logger.Printf("logged out")
399 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
400 var info string
401 if err := parseMessageParams(msg, nil, &info); err != nil {
402 return err
403 }
404 switch msg.Command {
405 case irc.ERR_NICKLOCKED:
406 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
407 case irc.ERR_SASLFAIL:
408 uc.logger.Printf("SASL authentication failed: %v", info)
409 case irc.ERR_SASLTOOLONG:
410 uc.logger.Printf("SASL message too long: %v", info)
411 }
412
413 uc.saslClient = nil
414 uc.saslStarted = false
415
416 uc.SendMessage(&irc.Message{
417 Command: "CAP",
418 Params: []string{"END"},
419 })
420 case irc.RPL_WELCOME:
421 uc.registered = true
422 uc.logger.Printf("connection registered")
423
424 for _, ch := range uc.network.channels {
425 params := []string{ch.Name}
426 if ch.Key != "" {
427 params = append(params, ch.Key)
428 }
429 uc.SendMessage(&irc.Message{
430 Command: "JOIN",
431 Params: params,
432 })
433 }
434 case irc.RPL_MYINFO:
435 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
436 return err
437 }
438 case irc.RPL_ISUPPORT:
439 if err := parseMessageParams(msg, nil, nil); err != nil {
440 return err
441 }
442 for _, token := range msg.Params[1 : len(msg.Params)-1] {
443 negate := false
444 parameter := token
445 value := ""
446 if strings.HasPrefix(token, "-") {
447 negate = true
448 token = token[1:]
449 } else {
450 if i := strings.IndexByte(token, '='); i >= 0 {
451 parameter = token[:i]
452 value = token[i+1:]
453 }
454 }
455 if !negate {
456 switch parameter {
457 case "CHANMODES":
458 parts := strings.SplitN(value, ",", 5)
459 if len(parts) < 4 {
460 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", value)
461 }
462 modes := make(map[byte]channelModeType)
463 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
464 for j := 0; j < len(parts[i]); j++ {
465 mode := parts[i][j]
466 modes[mode] = mt
467 }
468 }
469 uc.availableChannelModes = modes
470 case "CHANTYPES":
471 uc.availableChannelTypes = value
472 case "PREFIX":
473 if value == "" {
474 uc.availableMemberships = nil
475 } else {
476 if value[0] != '(' {
477 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
478 }
479 sep := strings.IndexByte(value, ')')
480 if sep < 0 || len(value) != sep*2 {
481 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
482 }
483 memberships := make([]membership, len(value)/2-1)
484 for i := range memberships {
485 memberships[i] = membership{
486 Mode: value[i+1],
487 Prefix: value[sep+i+1],
488 }
489 }
490 uc.availableMemberships = memberships
491 }
492 }
493 } else {
494 // TODO: handle ISUPPORT negations
495 }
496 }
497 case "BATCH":
498 var tag string
499 if err := parseMessageParams(msg, &tag); err != nil {
500 return err
501 }
502
503 if strings.HasPrefix(tag, "+") {
504 tag = tag[1:]
505 if _, ok := uc.batches[tag]; ok {
506 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
507 }
508 var batchType string
509 if err := parseMessageParams(msg, nil, &batchType); err != nil {
510 return err
511 }
512 label := label
513 if label == "" && msgBatch != nil {
514 label = msgBatch.Label
515 }
516 uc.batches[tag] = batch{
517 Type: batchType,
518 Params: msg.Params[2:],
519 Outer: msgBatch,
520 Label: label,
521 }
522 } else if strings.HasPrefix(tag, "-") {
523 tag = tag[1:]
524 if _, ok := uc.batches[tag]; !ok {
525 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
526 }
527 delete(uc.batches, tag)
528 } else {
529 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
530 }
531 case "NICK":
532 if msg.Prefix == nil {
533 return fmt.Errorf("expected a prefix")
534 }
535
536 var newNick string
537 if err := parseMessageParams(msg, &newNick); err != nil {
538 return err
539 }
540
541 me := false
542 if msg.Prefix.Name == uc.nick {
543 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
544 me = true
545 uc.nick = newNick
546 }
547
548 for _, ch := range uc.channels {
549 if membership, ok := ch.Members[msg.Prefix.Name]; ok {
550 delete(ch.Members, msg.Prefix.Name)
551 ch.Members[newNick] = membership
552 uc.appendLog(ch.Name, msg)
553 uc.appendHistory(ch.Name, msg)
554 }
555 }
556
557 if !me {
558 uc.forEachDownstream(func(dc *downstreamConn) {
559 dc.SendMessage(dc.marshalMessage(msg, uc.network))
560 })
561 }
562 case "JOIN":
563 if msg.Prefix == nil {
564 return fmt.Errorf("expected a prefix")
565 }
566
567 var channels string
568 if err := parseMessageParams(msg, &channels); err != nil {
569 return err
570 }
571
572 for _, ch := range strings.Split(channels, ",") {
573 if msg.Prefix.Name == uc.nick {
574 uc.logger.Printf("joined channel %q", ch)
575 uc.channels[ch] = &upstreamChannel{
576 Name: ch,
577 conn: uc,
578 Members: make(map[string]*membership),
579 }
580
581 uc.SendMessage(&irc.Message{
582 Command: "MODE",
583 Params: []string{ch},
584 })
585 } else {
586 ch, err := uc.getChannel(ch)
587 if err != nil {
588 return err
589 }
590 ch.Members[msg.Prefix.Name] = nil
591 }
592
593 chMsg := msg.Copy()
594 chMsg.Params[0] = ch
595 uc.produce(ch, chMsg, nil)
596 }
597 case "PART":
598 if msg.Prefix == nil {
599 return fmt.Errorf("expected a prefix")
600 }
601
602 var channels string
603 if err := parseMessageParams(msg, &channels); err != nil {
604 return err
605 }
606
607 for _, ch := range strings.Split(channels, ",") {
608 if msg.Prefix.Name == uc.nick {
609 uc.logger.Printf("parted channel %q", ch)
610 delete(uc.channels, ch)
611 } else {
612 ch, err := uc.getChannel(ch)
613 if err != nil {
614 return err
615 }
616 delete(ch.Members, msg.Prefix.Name)
617 }
618
619 chMsg := msg.Copy()
620 chMsg.Params[0] = ch
621 uc.produce(ch, chMsg, nil)
622 }
623 case "KICK":
624 if msg.Prefix == nil {
625 return fmt.Errorf("expected a prefix")
626 }
627
628 var channel, user string
629 if err := parseMessageParams(msg, &channel, &user); err != nil {
630 return err
631 }
632
633 if user == uc.nick {
634 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
635 delete(uc.channels, channel)
636 } else {
637 ch, err := uc.getChannel(channel)
638 if err != nil {
639 return err
640 }
641 delete(ch.Members, user)
642 }
643
644 uc.produce(channel, msg, nil)
645 case "QUIT":
646 if msg.Prefix == nil {
647 return fmt.Errorf("expected a prefix")
648 }
649
650 if msg.Prefix.Name == uc.nick {
651 uc.logger.Printf("quit")
652 }
653
654 for _, ch := range uc.channels {
655 if _, ok := ch.Members[msg.Prefix.Name]; ok {
656 delete(ch.Members, msg.Prefix.Name)
657
658 uc.appendLog(ch.Name, msg)
659 uc.appendHistory(ch.Name, msg)
660 }
661 }
662
663 if msg.Prefix.Name != uc.nick {
664 uc.forEachDownstream(func(dc *downstreamConn) {
665 dc.SendMessage(dc.marshalMessage(msg, uc.network))
666 })
667 }
668 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
669 var name, topic string
670 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
671 return err
672 }
673 ch, err := uc.getChannel(name)
674 if err != nil {
675 return err
676 }
677 if msg.Command == irc.RPL_TOPIC {
678 ch.Topic = topic
679 } else {
680 ch.Topic = ""
681 }
682 case "TOPIC":
683 var name string
684 if err := parseMessageParams(msg, &name); err != nil {
685 return err
686 }
687 ch, err := uc.getChannel(name)
688 if err != nil {
689 return err
690 }
691 if len(msg.Params) > 1 {
692 ch.Topic = msg.Params[1]
693 } else {
694 ch.Topic = ""
695 }
696 uc.produce(ch.Name, msg, nil)
697 case "MODE":
698 var name, modeStr string
699 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
700 return err
701 }
702
703 if !uc.isChannel(name) { // user mode change
704 if name != uc.nick {
705 return fmt.Errorf("received MODE message for unknown nick %q", name)
706 }
707 return uc.modes.Apply(modeStr)
708 // TODO: notify downstreams about user mode change?
709 } else { // channel mode change
710 ch, err := uc.getChannel(name)
711 if err != nil {
712 return err
713 }
714
715 if ch.modes != nil {
716 if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[2:]...); err != nil {
717 return err
718 }
719 }
720
721 uc.produce(ch.Name, msg, nil)
722 }
723 case irc.RPL_UMODEIS:
724 if err := parseMessageParams(msg, nil); err != nil {
725 return err
726 }
727 modeStr := ""
728 if len(msg.Params) > 1 {
729 modeStr = msg.Params[1]
730 }
731
732 uc.modes = ""
733 if err := uc.modes.Apply(modeStr); err != nil {
734 return err
735 }
736 // TODO: send RPL_UMODEIS to downstream connections when applicable
737 case irc.RPL_CHANNELMODEIS:
738 var channel string
739 if err := parseMessageParams(msg, nil, &channel); err != nil {
740 return err
741 }
742 modeStr := ""
743 if len(msg.Params) > 2 {
744 modeStr = msg.Params[2]
745 }
746
747 ch, err := uc.getChannel(channel)
748 if err != nil {
749 return err
750 }
751
752 firstMode := ch.modes == nil
753 ch.modes = make(map[byte]string)
754 if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[3:]...); err != nil {
755 return err
756 }
757 if firstMode {
758 modeStr, modeParams := ch.modes.Format()
759
760 uc.forEachDownstream(func(dc *downstreamConn) {
761 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
762 params = append(params, modeParams...)
763
764 dc.SendMessage(&irc.Message{
765 Prefix: dc.srv.prefix(),
766 Command: irc.RPL_CHANNELMODEIS,
767 Params: params,
768 })
769 })
770 }
771 case rpl_creationtime:
772 var channel, creationTime string
773 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
774 return err
775 }
776
777 ch, err := uc.getChannel(channel)
778 if err != nil {
779 return err
780 }
781
782 firstCreationTime := ch.creationTime == ""
783 ch.creationTime = creationTime
784 if firstCreationTime {
785 uc.forEachDownstream(func(dc *downstreamConn) {
786 dc.SendMessage(&irc.Message{
787 Prefix: dc.srv.prefix(),
788 Command: rpl_creationtime,
789 Params: []string{dc.nick, channel, creationTime},
790 })
791 })
792 }
793 case rpl_topicwhotime:
794 var name, who, timeStr string
795 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
796 return err
797 }
798 ch, err := uc.getChannel(name)
799 if err != nil {
800 return err
801 }
802 ch.TopicWho = who
803 sec, err := strconv.ParseInt(timeStr, 10, 64)
804 if err != nil {
805 return fmt.Errorf("failed to parse topic time: %v", err)
806 }
807 ch.TopicTime = time.Unix(sec, 0)
808 case irc.RPL_LIST:
809 var channel, clients, topic string
810 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
811 return err
812 }
813
814 pl := uc.getPendingLIST()
815 if pl == nil {
816 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
817 }
818
819 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
820 dc.SendMessage(&irc.Message{
821 Prefix: dc.srv.prefix(),
822 Command: irc.RPL_LIST,
823 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
824 })
825 })
826 case irc.RPL_LISTEND:
827 ok := uc.endPendingLISTs(false)
828 if !ok {
829 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
830 }
831 case irc.RPL_NAMREPLY:
832 var name, statusStr, members string
833 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
834 return err
835 }
836
837 ch, ok := uc.channels[name]
838 if !ok {
839 // NAMES on a channel we have not joined, forward to downstream
840 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
841 channel := dc.marshalEntity(uc.network, name)
842 members := splitSpace(members)
843 for i, member := range members {
844 membership, nick := uc.parseMembershipPrefix(member)
845 members[i] = membership.String() + dc.marshalEntity(uc.network, nick)
846 }
847 memberStr := strings.Join(members, " ")
848
849 dc.SendMessage(&irc.Message{
850 Prefix: dc.srv.prefix(),
851 Command: irc.RPL_NAMREPLY,
852 Params: []string{dc.nick, statusStr, channel, memberStr},
853 })
854 })
855 return nil
856 }
857
858 status, err := parseChannelStatus(statusStr)
859 if err != nil {
860 return err
861 }
862 ch.Status = status
863
864 for _, s := range splitSpace(members) {
865 membership, nick := uc.parseMembershipPrefix(s)
866 ch.Members[nick] = membership
867 }
868 case irc.RPL_ENDOFNAMES:
869 var name string
870 if err := parseMessageParams(msg, nil, &name); err != nil {
871 return err
872 }
873
874 ch, ok := uc.channels[name]
875 if !ok {
876 // NAMES on a channel we have not joined, forward to downstream
877 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
878 channel := dc.marshalEntity(uc.network, name)
879
880 dc.SendMessage(&irc.Message{
881 Prefix: dc.srv.prefix(),
882 Command: irc.RPL_ENDOFNAMES,
883 Params: []string{dc.nick, channel, "End of /NAMES list"},
884 })
885 })
886 return nil
887 }
888
889 if ch.complete {
890 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
891 }
892 ch.complete = true
893
894 uc.forEachDownstream(func(dc *downstreamConn) {
895 forwardChannel(dc, ch)
896 })
897 case irc.RPL_WHOREPLY:
898 var channel, username, host, server, nick, mode, trailing string
899 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &mode, &trailing); err != nil {
900 return err
901 }
902
903 parts := strings.SplitN(trailing, " ", 2)
904 if len(parts) != 2 {
905 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong trailing parameter: %s", trailing)
906 }
907 realname := parts[1]
908 hops, err := strconv.Atoi(parts[0])
909 if err != nil {
910 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong hop count: %s", parts[0])
911 }
912 hops++
913
914 trailing = strconv.Itoa(hops) + " " + realname
915
916 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
917 channel := channel
918 if channel != "*" {
919 channel = dc.marshalEntity(uc.network, channel)
920 }
921 nick := dc.marshalEntity(uc.network, nick)
922 dc.SendMessage(&irc.Message{
923 Prefix: dc.srv.prefix(),
924 Command: irc.RPL_WHOREPLY,
925 Params: []string{dc.nick, channel, username, host, server, nick, mode, trailing},
926 })
927 })
928 case irc.RPL_ENDOFWHO:
929 var name string
930 if err := parseMessageParams(msg, nil, &name); err != nil {
931 return err
932 }
933
934 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
935 name := name
936 if name != "*" {
937 // TODO: support WHO masks
938 name = dc.marshalEntity(uc.network, name)
939 }
940 dc.SendMessage(&irc.Message{
941 Prefix: dc.srv.prefix(),
942 Command: irc.RPL_ENDOFWHO,
943 Params: []string{dc.nick, name, "End of /WHO list"},
944 })
945 })
946 case irc.RPL_WHOISUSER:
947 var nick, username, host, realname string
948 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
949 return err
950 }
951
952 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
953 nick := dc.marshalEntity(uc.network, nick)
954 dc.SendMessage(&irc.Message{
955 Prefix: dc.srv.prefix(),
956 Command: irc.RPL_WHOISUSER,
957 Params: []string{dc.nick, nick, username, host, "*", realname},
958 })
959 })
960 case irc.RPL_WHOISSERVER:
961 var nick, server, serverInfo string
962 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
963 return err
964 }
965
966 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
967 nick := dc.marshalEntity(uc.network, nick)
968 dc.SendMessage(&irc.Message{
969 Prefix: dc.srv.prefix(),
970 Command: irc.RPL_WHOISSERVER,
971 Params: []string{dc.nick, nick, server, serverInfo},
972 })
973 })
974 case irc.RPL_WHOISOPERATOR:
975 var nick string
976 if err := parseMessageParams(msg, nil, &nick); err != nil {
977 return err
978 }
979
980 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
981 nick := dc.marshalEntity(uc.network, nick)
982 dc.SendMessage(&irc.Message{
983 Prefix: dc.srv.prefix(),
984 Command: irc.RPL_WHOISOPERATOR,
985 Params: []string{dc.nick, nick, "is an IRC operator"},
986 })
987 })
988 case irc.RPL_WHOISIDLE:
989 var nick string
990 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
991 return err
992 }
993
994 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
995 nick := dc.marshalEntity(uc.network, nick)
996 params := []string{dc.nick, nick}
997 params = append(params, msg.Params[2:]...)
998 dc.SendMessage(&irc.Message{
999 Prefix: dc.srv.prefix(),
1000 Command: irc.RPL_WHOISIDLE,
1001 Params: params,
1002 })
1003 })
1004 case irc.RPL_WHOISCHANNELS:
1005 var nick, channelList string
1006 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1007 return err
1008 }
1009 channels := splitSpace(channelList)
1010
1011 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1012 nick := dc.marshalEntity(uc.network, nick)
1013 channelList := make([]string, len(channels))
1014 for i, channel := range channels {
1015 prefix, channel := uc.parseMembershipPrefix(channel)
1016 channel = dc.marshalEntity(uc.network, channel)
1017 channelList[i] = prefix.String() + channel
1018 }
1019 channels := strings.Join(channelList, " ")
1020 dc.SendMessage(&irc.Message{
1021 Prefix: dc.srv.prefix(),
1022 Command: irc.RPL_WHOISCHANNELS,
1023 Params: []string{dc.nick, nick, channels},
1024 })
1025 })
1026 case irc.RPL_ENDOFWHOIS:
1027 var nick string
1028 if err := parseMessageParams(msg, nil, &nick); err != nil {
1029 return err
1030 }
1031
1032 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1033 nick := dc.marshalEntity(uc.network, nick)
1034 dc.SendMessage(&irc.Message{
1035 Prefix: dc.srv.prefix(),
1036 Command: irc.RPL_ENDOFWHOIS,
1037 Params: []string{dc.nick, nick, "End of /WHOIS list"},
1038 })
1039 })
1040 case "PRIVMSG":
1041 if msg.Prefix == nil {
1042 return fmt.Errorf("expected a prefix")
1043 }
1044
1045 var entity, text string
1046 if err := parseMessageParams(msg, &entity, &text); err != nil {
1047 return err
1048 }
1049
1050 if msg.Prefix.Name == serviceNick {
1051 uc.logger.Printf("skipping PRIVMSG from soju's service: %v", msg)
1052 break
1053 }
1054 if entity == serviceNick {
1055 uc.logger.Printf("skipping PRIVMSG to soju's service: %v", msg)
1056 break
1057 }
1058
1059 target := entity
1060 if target == uc.nick {
1061 target = msg.Prefix.Name
1062 }
1063 uc.produce(target, msg, nil)
1064 case "INVITE":
1065 var nick string
1066 var channel string
1067 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1068 return err
1069 }
1070
1071 uc.forEachDownstream(func(dc *downstreamConn) {
1072 dc.SendMessage(&irc.Message{
1073 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1074 Command: "INVITE",
1075 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1076 })
1077 })
1078 case irc.RPL_INVITING:
1079 var nick string
1080 var channel string
1081 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1082 return err
1083 }
1084
1085 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1086 dc.SendMessage(&irc.Message{
1087 Prefix: dc.srv.prefix(),
1088 Command: irc.RPL_INVITING,
1089 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1090 })
1091 })
1092 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1093 var command, reason string
1094 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1095 return err
1096 }
1097
1098 if command == "LIST" {
1099 ok := uc.endPendingLISTs(false)
1100 if !ok {
1101 return fmt.Errorf("unexpected response for LIST: %q: no matching pending LIST", msg.Command)
1102 }
1103 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1104 dc.SendMessage(&irc.Message{
1105 Prefix: uc.srv.prefix(),
1106 Command: msg.Command,
1107 Params: []string{dc.nick, "LIST", reason},
1108 })
1109 })
1110 }
1111 case "TAGMSG":
1112 // TODO: relay to downstream connections that accept message-tags
1113 case "ACK":
1114 // Ignore
1115 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1116 // Ignore
1117 case irc.RPL_YOURHOST, irc.RPL_CREATED:
1118 // Ignore
1119 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
1120 // Ignore
1121 case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
1122 // Ignore
1123 case irc.RPL_LISTSTART:
1124 // Ignore
1125 case rpl_localusers, rpl_globalusers:
1126 // Ignore
1127 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
1128 // Ignore
1129 default:
1130 uc.logger.Printf("unhandled message: %v", msg)
1131 }
1132 return nil
1133}
1134
1135func splitSpace(s string) []string {
1136 return strings.FieldsFunc(s, func(r rune) bool {
1137 return r == ' '
1138 })
1139}
1140
1141func (uc *upstreamConn) register() {
1142 uc.nick = uc.network.Nick
1143 uc.username = uc.network.Username
1144 if uc.username == "" {
1145 uc.username = uc.nick
1146 }
1147 uc.realname = uc.network.Realname
1148 if uc.realname == "" {
1149 uc.realname = uc.nick
1150 }
1151
1152 uc.SendMessage(&irc.Message{
1153 Command: "CAP",
1154 Params: []string{"LS", "302"},
1155 })
1156
1157 if uc.network.Pass != "" {
1158 uc.SendMessage(&irc.Message{
1159 Command: "PASS",
1160 Params: []string{uc.network.Pass},
1161 })
1162 }
1163
1164 uc.SendMessage(&irc.Message{
1165 Command: "NICK",
1166 Params: []string{uc.nick},
1167 })
1168 uc.SendMessage(&irc.Message{
1169 Command: "USER",
1170 Params: []string{uc.username, "0", "*", uc.realname},
1171 })
1172}
1173
1174func (uc *upstreamConn) runUntilRegistered() error {
1175 for !uc.registered {
1176 msg, err := uc.ReadMessage()
1177 if err != nil {
1178 return fmt.Errorf("failed to read message: %v", err)
1179 }
1180
1181 if err := uc.handleMessage(msg); err != nil {
1182 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1183 }
1184 }
1185
1186 for _, command := range uc.network.ConnectCommands {
1187 m, err := irc.ParseMessage(command)
1188 if err != nil {
1189 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1190 } else {
1191 uc.SendMessage(m)
1192 }
1193 }
1194
1195 return nil
1196}
1197
1198func (uc *upstreamConn) requestSASL() bool {
1199 if uc.network.SASL.Mechanism == "" {
1200 return false
1201 }
1202
1203 v, ok := uc.caps["sasl"]
1204 if !ok {
1205 return false
1206 }
1207 if v != "" {
1208 mechanisms := strings.Split(v, ",")
1209 found := false
1210 for _, mech := range mechanisms {
1211 if strings.EqualFold(mech, uc.network.SASL.Mechanism) {
1212 found = true
1213 break
1214 }
1215 }
1216 if !found {
1217 return false
1218 }
1219 }
1220
1221 return true
1222}
1223
1224func (uc *upstreamConn) handleCapAck(name string, ok bool) error {
1225 auth := &uc.network.SASL
1226 switch name {
1227 case "sasl":
1228 if !ok {
1229 uc.logger.Printf("server refused to acknowledge the SASL capability")
1230 return nil
1231 }
1232
1233 switch auth.Mechanism {
1234 case "PLAIN":
1235 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1236 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
1237 default:
1238 return fmt.Errorf("unsupported SASL mechanism %q", name)
1239 }
1240
1241 uc.SendMessage(&irc.Message{
1242 Command: "AUTHENTICATE",
1243 Params: []string{auth.Mechanism},
1244 })
1245 case "message-tags":
1246 uc.tagsSupported = ok
1247 case "labeled-response":
1248 uc.labelsSupported = ok
1249 case "batch", "server-time":
1250 // Nothing to do
1251 default:
1252 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
1253 }
1254 return nil
1255}
1256
1257func (uc *upstreamConn) readMessages(ch chan<- event) error {
1258 for {
1259 msg, err := uc.ReadMessage()
1260 if err == io.EOF {
1261 break
1262 } else if err != nil {
1263 return fmt.Errorf("failed to read IRC command: %v", err)
1264 }
1265
1266 ch <- eventUpstreamMessage{msg, uc}
1267 }
1268
1269 return nil
1270}
1271
1272func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
1273 if uc.labelsSupported {
1274 if msg.Tags == nil {
1275 msg.Tags = make(map[string]irc.TagValue)
1276 }
1277 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
1278 uc.nextLabelID++
1279 }
1280 uc.SendMessage(msg)
1281}
1282
1283// TODO: handle moving logs when a network name changes, when support for this is added
1284func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
1285 if uc.srv.LogPath == "" {
1286 return
1287 }
1288
1289 ml, ok := uc.messageLoggers[entity]
1290 if !ok {
1291 ml = newMessageLogger(uc.network, entity)
1292 uc.messageLoggers[entity] = ml
1293 }
1294
1295 if err := ml.Append(msg); err != nil {
1296 uc.logger.Printf("failed to log message: %v", err)
1297 }
1298}
1299
1300// appendHistory appends a message to the history. entity can be empty.
1301func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
1302 // If no client is offline, no need to append the message to the buffer
1303 if len(uc.network.offlineClients) == 0 {
1304 return
1305 }
1306
1307 history, ok := uc.network.history[entity]
1308 if !ok {
1309 history = &networkHistory{
1310 offlineClients: make(map[string]uint64),
1311 ring: NewRing(uc.srv.RingCap),
1312 }
1313 uc.network.history[entity] = history
1314
1315 for clientName, _ := range uc.network.offlineClients {
1316 history.offlineClients[clientName] = 0
1317 }
1318 }
1319
1320 history.ring.Produce(msg)
1321}
1322
1323// produce appends a message to the logs, adds it to the history and forwards
1324// it to connected downstream connections.
1325//
1326// If origin is not nil and origin doesn't support echo-message, the message is
1327// forwarded to all connections except origin.
1328func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
1329 if target != "" {
1330 uc.appendLog(target, msg)
1331 }
1332
1333 uc.appendHistory(target, msg)
1334
1335 uc.forEachDownstream(func(dc *downstreamConn) {
1336 if dc != origin || dc.caps["echo-message"] {
1337 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1338 }
1339 })
1340}
1341
1342func (uc *upstreamConn) updateAway() {
1343 away := true
1344 uc.forEachDownstream(func(*downstreamConn) {
1345 away = false
1346 })
1347 if away == uc.away {
1348 return
1349 }
1350 if away {
1351 uc.SendMessage(&irc.Message{
1352 Command: "AWAY",
1353 Params: []string{"Auto away"},
1354 })
1355 } else {
1356 uc.SendMessage(&irc.Message{
1357 Command: "AWAY",
1358 })
1359 }
1360 uc.away = away
1361}
Note: See TracBrowser for help on using the repository browser.