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
RevLine 
[98]1package soju
[13]2
3import (
4 "crypto/tls"
[95]5 "encoding/base64"
[155]6 "errors"
[13]7 "fmt"
8 "io"
9 "net"
[19]10 "strconv"
[17]11 "strings"
[19]12 "time"
[13]13
[95]14 "github.com/emersion/go-sasl"
[13]15 "gopkg.in/irc.v3"
16)
17
[19]18type upstreamChannel struct {
[162]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
[19]29}
30
[13]31type upstreamConn struct {
[210]32 conn
[16]33
[210]34 network *network
35 user *user
36
[16]37 serverName string
38 availableUserModes string
[139]39 availableChannelModes map[byte]channelModeType
40 availableChannelTypes string
41 availableMemberships []membership
[19]42
43 registered bool
[42]44 nick string
[77]45 username string
46 realname string
[139]47 modes userModes
[19]48 channels map[string]*upstreamChannel
[92]49 caps map[string]string
[153]50 batches map[string]batch
[198]51 away bool
[95]52
[155]53 tagsSupported bool
54 labelsSupported bool
[161]55 nextLabelID uint64
[152]56
[95]57 saslClient sasl.Client
58 saslStarted bool
[177]59
60 // set of LIST commands in progress, per downstream
61 pendingLISTDownstreamSet map[uint64]struct{}
[178]62
[215]63 messageLoggers map[string]*messageLogger
[13]64}
65
[77]66func connectToUpstream(network *network) (*upstreamConn, error) {
67 logger := &prefixLogger{network.user.srv.Logger, fmt.Sprintf("upstream %q: ", network.Addr)}
[33]68
[77]69 addr := network.Addr
70 if !strings.ContainsRune(addr, ':') {
71 addr = addr + ":6697"
72 }
73
[206]74 dialer := net.Dialer{Timeout: connectTimeout}
75
[77]76 logger.Printf("connecting to TLS server at address %q", addr)
[206]77 netConn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
[33]78 if err != nil {
[77]79 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
[33]80 }
81
[55]82 uc := &upstreamConn{
[210]83 conn: *newConn(network.user.srv, netConn, logger),
[177]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{}),
[215]93 messageLoggers: make(map[string]*messageLogger),
[33]94 }
95
[55]96 return uc, nil
[33]97}
98
[73]99func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) {
[218]100 uc.network.forEachDownstream(f)
[73]101}
102
[161]103func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) {
[155]104 uc.forEachDownstream(func(dc *downstreamConn) {
105 if id != 0 && id != dc.id {
106 return
107 }
108 f(dc)
109 })
110}
111
[55]112func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
113 ch, ok := uc.channels[name]
[19]114 if !ok {
115 return nil, fmt.Errorf("unknown channel %q", name)
116 }
117 return ch, nil
118}
119
[129]120func (uc *upstreamConn) isChannel(entity string) bool {
[139]121 if i := strings.IndexByte(uc.availableChannelTypes, entity[0]); i >= 0 {
122 return true
[129]123 }
124 return false
125}
126
[181]127func (uc *upstreamConn) getPendingLIST() *pendingLIST {
[177]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
[181]137func (uc *upstreamConn) endPendingLISTs(all bool) (found bool) {
[177]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) {
[181]160 uc.trySendLIST(pl.downstreamID)
[177]161 })
162 return
163 }
164 }
165 return
166}
167
[181]168func (uc *upstreamConn) trySendLIST(downstreamID uint64) {
[177]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
[139]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
[55]205func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
[155]206 var label string
207 if l, ok := msg.GetTag("label"); ok {
208 label = l
209 }
210
[153]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
[155]218 if label == "" {
219 label = msgBatch.Label
220 }
[153]221 }
222
[161]223 var downstreamID uint64 = 0
[155]224 if label != "" {
225 var labelOffset uint64
[161]226 n, err := fmt.Sscanf(label, "sd-%d-%d", &downstreamID, &labelOffset)
[155]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
[216]235 if _, ok := msg.Tags["time"]; !ok {
[240]236 msg.Tags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
[216]237 }
238
[13]239 switch msg.Command {
240 case "PING":
[60]241 uc.SendMessage(&irc.Message{
[13]242 Command: "PONG",
[68]243 Params: msg.Params,
[60]244 })
[33]245 return nil
[18]246 case "NOTICE":
[171]247 if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
[239]248 uc.produce("", msg, nil)
[171]249 } else { // regular user NOTICE
[217]250 var entity, text string
251 if err := parseMessageParams(msg, &entity, &text); err != nil {
[171]252 return err
253 }
254
[217]255 target := entity
256 if target == uc.nick {
[178]257 target = msg.Prefix.Name
258 }
[239]259 uc.produce(target, msg, nil)
[171]260 }
[92]261 case "CAP":
[95]262 var subCmd string
263 if err := parseMessageParams(msg, nil, &subCmd); err != nil {
264 return err
[92]265 }
[95]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] == "*"
[92]275
[95]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
[92]284 }
285
[95]286 if more {
287 break // wait to receive all capabilities
288 }
289
[152]290 requestCaps := make([]string, 0, 16)
[193]291 for _, c := range []string{"message-tags", "batch", "labeled-response", "server-time"} {
[152]292 if _, ok := uc.caps[c]; ok {
293 requestCaps = append(requestCaps, c)
294 }
295 }
296
[95]297 if uc.requestSASL() {
[152]298 requestCaps = append(requestCaps, "sasl")
299 }
300
301 if len(requestCaps) > 0 {
[95]302 uc.SendMessage(&irc.Message{
303 Command: "CAP",
[152]304 Params: []string{"REQ", strings.Join(requestCaps, " ")},
[95]305 })
[152]306 }
307
308 if uc.requestSASL() {
[95]309 break // we'll send CAP END after authentication is completed
310 }
311
[92]312 uc.SendMessage(&irc.Message{
313 Command: "CAP",
314 Params: []string{"END"},
315 })
[95]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)
[92]336 }
[95]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 })
[125]391 case irc.RPL_LOGGEDIN:
[95]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)
[125]397 case irc.RPL_LOGGEDOUT:
[95]398 uc.logger.Printf("logged out")
[125]399 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
[95]400 var info string
401 if err := parseMessageParams(msg, nil, &info); err != nil {
402 return err
403 }
404 switch msg.Command {
[125]405 case irc.ERR_NICKLOCKED:
[95]406 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
[125]407 case irc.ERR_SASLFAIL:
[95]408 uc.logger.Printf("SASL authentication failed: %v", info)
[125]409 case irc.ERR_SASLTOOLONG:
[95]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 })
[14]420 case irc.RPL_WELCOME:
[55]421 uc.registered = true
422 uc.logger.Printf("connection registered")
[19]423
[267]424 for _, ch := range uc.network.channels {
[146]425 params := []string{ch.Name}
426 if ch.Key != "" {
427 params = append(params, ch.Key)
428 }
[60]429 uc.SendMessage(&irc.Message{
[19]430 Command: "JOIN",
[146]431 Params: params,
[60]432 })
[19]433 }
[16]434 case irc.RPL_MYINFO:
[139]435 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
[43]436 return err
[16]437 }
[139]438 case irc.RPL_ISUPPORT:
439 if err := parseMessageParams(msg, nil, nil); err != nil {
440 return err
[16]441 }
[139]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 }
[153]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 }
[155]512 label := label
513 if label == "" && msgBatch != nil {
514 label = msgBatch.Label
515 }
[153]516 uc.batches[tag] = batch{
517 Type: batchType,
518 Params: msg.Params[2:],
519 Outer: msgBatch,
[155]520 Label: label,
[153]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 }
[42]531 case "NICK":
[83]532 if msg.Prefix == nil {
533 return fmt.Errorf("expected a prefix")
534 }
535
[43]536 var newNick string
537 if err := parseMessageParams(msg, &newNick); err != nil {
538 return err
[42]539 }
540
[244]541 me := false
[55]542 if msg.Prefix.Name == uc.nick {
543 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
[244]544 me = true
[55]545 uc.nick = newNick
[42]546 }
547
[55]548 for _, ch := range uc.channels {
[42]549 if membership, ok := ch.Members[msg.Prefix.Name]; ok {
550 delete(ch.Members, msg.Prefix.Name)
551 ch.Members[newNick] = membership
[215]552 uc.appendLog(ch.Name, msg)
[253]553 uc.appendHistory(ch.Name, msg)
[42]554 }
555 }
[82]556
[244]557 if !me {
[82]558 uc.forEachDownstream(func(dc *downstreamConn) {
[261]559 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[82]560 })
561 }
[69]562 case "JOIN":
563 if msg.Prefix == nil {
564 return fmt.Errorf("expected a prefix")
565 }
[42]566
[43]567 var channels string
568 if err := parseMessageParams(msg, &channels); err != nil {
569 return err
[19]570 }
[34]571
[43]572 for _, ch := range strings.Split(channels, ",") {
[55]573 if msg.Prefix.Name == uc.nick {
574 uc.logger.Printf("joined channel %q", ch)
575 uc.channels[ch] = &upstreamChannel{
[34]576 Name: ch,
[55]577 conn: uc,
[139]578 Members: make(map[string]*membership),
[34]579 }
[139]580
581 uc.SendMessage(&irc.Message{
582 Command: "MODE",
583 Params: []string{ch},
584 })
[34]585 } else {
[55]586 ch, err := uc.getChannel(ch)
[34]587 if err != nil {
588 return err
589 }
[139]590 ch.Members[msg.Prefix.Name] = nil
[19]591 }
[69]592
[245]593 chMsg := msg.Copy()
594 chMsg.Params[0] = ch
595 uc.produce(ch, chMsg, nil)
[19]596 }
[69]597 case "PART":
598 if msg.Prefix == nil {
599 return fmt.Errorf("expected a prefix")
600 }
[34]601
[43]602 var channels string
603 if err := parseMessageParams(msg, &channels); err != nil {
604 return err
[34]605 }
606
[43]607 for _, ch := range strings.Split(channels, ",") {
[55]608 if msg.Prefix.Name == uc.nick {
609 uc.logger.Printf("parted channel %q", ch)
610 delete(uc.channels, ch)
[34]611 } else {
[55]612 ch, err := uc.getChannel(ch)
[34]613 if err != nil {
614 return err
615 }
616 delete(ch.Members, msg.Prefix.Name)
617 }
[69]618
[245]619 chMsg := msg.Copy()
620 chMsg.Params[0] = ch
621 uc.produce(ch, chMsg, nil)
[34]622 }
[159]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
[245]644 uc.produce(channel, msg, nil)
[83]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 {
[178]655 if _, ok := ch.Members[msg.Prefix.Name]; ok {
656 delete(ch.Members, msg.Prefix.Name)
657
[215]658 uc.appendLog(ch.Name, msg)
[253]659 uc.appendHistory(ch.Name, msg)
[178]660 }
[83]661 }
662
663 if msg.Prefix.Name != uc.nick {
664 uc.forEachDownstream(func(dc *downstreamConn) {
[261]665 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[83]666 })
667 }
[19]668 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
[43]669 var name, topic string
670 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
671 return err
[19]672 }
[55]673 ch, err := uc.getChannel(name)
[19]674 if err != nil {
675 return err
676 }
677 if msg.Command == irc.RPL_TOPIC {
[43]678 ch.Topic = topic
[19]679 } else {
680 ch.Topic = ""
681 }
682 case "TOPIC":
[43]683 var name string
[74]684 if err := parseMessageParams(msg, &name); err != nil {
[43]685 return err
[19]686 }
[55]687 ch, err := uc.getChannel(name)
[19]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 }
[245]696 uc.produce(ch.Name, msg, nil)
[139]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
[245]721 uc.produce(ch.Name, msg, nil)
[139]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) {
[260]761 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
[139]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 }
[162]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 }
[19]793 case rpl_topicwhotime:
[43]794 var name, who, timeStr string
795 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
796 return err
[19]797 }
[55]798 ch, err := uc.getChannel(name)
[19]799 if err != nil {
800 return err
801 }
[43]802 ch.TopicWho = who
803 sec, err := strconv.ParseInt(timeStr, 10, 64)
[19]804 if err != nil {
805 return fmt.Errorf("failed to parse topic time: %v", err)
806 }
807 ch.TopicTime = time.Unix(sec, 0)
[177]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
[181]814 pl := uc.getPendingLIST()
[177]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,
[260]823 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
[177]824 })
825 })
826 case irc.RPL_LISTEND:
[181]827 ok := uc.endPendingLISTs(false)
[177]828 if !ok {
829 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
830 }
[19]831 case irc.RPL_NAMREPLY:
[43]832 var name, statusStr, members string
833 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
834 return err
[19]835 }
[140]836
837 ch, ok := uc.channels[name]
838 if !ok {
839 // NAMES on a channel we have not joined, forward to downstream
[161]840 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]841 channel := dc.marshalEntity(uc.network, name)
[174]842 members := splitSpace(members)
[140]843 for i, member := range members {
844 membership, nick := uc.parseMembershipPrefix(member)
[260]845 members[i] = membership.String() + dc.marshalEntity(uc.network, nick)
[140]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
[19]856 }
857
[43]858 status, err := parseChannelStatus(statusStr)
[19]859 if err != nil {
860 return err
861 }
862 ch.Status = status
863
[174]864 for _, s := range splitSpace(members) {
[139]865 membership, nick := uc.parseMembershipPrefix(s)
[19]866 ch.Members[nick] = membership
867 }
868 case irc.RPL_ENDOFNAMES:
[43]869 var name string
870 if err := parseMessageParams(msg, nil, &name); err != nil {
871 return err
[25]872 }
[140]873
874 ch, ok := uc.channels[name]
875 if !ok {
876 // NAMES on a channel we have not joined, forward to downstream
[161]877 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]878 channel := dc.marshalEntity(uc.network, name)
[140]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
[25]887 }
888
[34]889 if ch.complete {
890 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
891 }
[25]892 ch.complete = true
[27]893
[73]894 uc.forEachDownstream(func(dc *downstreamConn) {
[27]895 forwardChannel(dc, ch)
[40]896 })
[127]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
[161]916 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[127]917 channel := channel
918 if channel != "*" {
[260]919 channel = dc.marshalEntity(uc.network, channel)
[127]920 }
[260]921 nick := dc.marshalEntity(uc.network, nick)
[127]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
[161]934 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[127]935 name := name
936 if name != "*" {
937 // TODO: support WHO masks
[260]938 name = dc.marshalEntity(uc.network, name)
[127]939 }
940 dc.SendMessage(&irc.Message{
941 Prefix: dc.srv.prefix(),
942 Command: irc.RPL_ENDOFWHO,
[142]943 Params: []string{dc.nick, name, "End of /WHO list"},
[127]944 })
945 })
[128]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
[161]952 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]953 nick := dc.marshalEntity(uc.network, nick)
[128]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
[161]966 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]967 nick := dc.marshalEntity(uc.network, nick)
[128]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
[161]980 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]981 nick := dc.marshalEntity(uc.network, nick)
[128]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
[161]994 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]995 nick := dc.marshalEntity(uc.network, nick)
[128]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 }
[174]1009 channels := splitSpace(channelList)
[128]1010
[161]1011 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1012 nick := dc.marshalEntity(uc.network, nick)
[128]1013 channelList := make([]string, len(channels))
1014 for i, channel := range channels {
[139]1015 prefix, channel := uc.parseMembershipPrefix(channel)
[260]1016 channel = dc.marshalEntity(uc.network, channel)
[128]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
[161]1032 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1033 nick := dc.marshalEntity(uc.network, nick)
[128]1034 dc.SendMessage(&irc.Message{
1035 Prefix: dc.srv.prefix(),
1036 Command: irc.RPL_ENDOFWHOIS,
[142]1037 Params: []string{dc.nick, nick, "End of /WHOIS list"},
[128]1038 })
1039 })
[36]1040 case "PRIVMSG":
[117]1041 if msg.Prefix == nil {
1042 return fmt.Errorf("expected a prefix")
1043 }
1044
[217]1045 var entity, text string
1046 if err := parseMessageParams(msg, &entity, &text); err != nil {
[69]1047 return err
1048 }
[117]1049
1050 if msg.Prefix.Name == serviceNick {
1051 uc.logger.Printf("skipping PRIVMSG from soju's service: %v", msg)
1052 break
1053 }
[217]1054 if entity == serviceNick {
[117]1055 uc.logger.Printf("skipping PRIVMSG to soju's service: %v", msg)
1056 break
1057 }
1058
[217]1059 target := entity
1060 if target == uc.nick {
[178]1061 target = msg.Prefix.Name
1062 }
[239]1063 uc.produce(target, msg, nil)
[115]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{
[260]1073 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
[115]1074 Command: "INVITE",
[260]1075 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
[115]1076 })
1077 })
[163]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,
[260]1089 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
[163]1090 })
1091 })
[177]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" {
[181]1099 ok := uc.endPendingLISTs(false)
[177]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 }
[152]1111 case "TAGMSG":
1112 // TODO: relay to downstream connections that accept message-tags
[155]1113 case "ACK":
1114 // Ignore
[198]1115 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1116 // Ignore
[16]1117 case irc.RPL_YOURHOST, irc.RPL_CREATED:
[14]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
[177]1123 case irc.RPL_LISTSTART:
1124 // Ignore
[14]1125 case rpl_localusers, rpl_globalusers:
1126 // Ignore
[96]1127 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
[14]1128 // Ignore
[13]1129 default:
[95]1130 uc.logger.Printf("unhandled message: %v", msg)
[13]1131 }
[14]1132 return nil
[13]1133}
1134
[174]1135func splitSpace(s string) []string {
1136 return strings.FieldsFunc(s, func(r rune) bool {
1137 return r == ' '
1138 })
1139}
1140
[55]1141func (uc *upstreamConn) register() {
[77]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
[60]1152 uc.SendMessage(&irc.Message{
[92]1153 Command: "CAP",
1154 Params: []string{"LS", "302"},
1155 })
1156
[93]1157 if uc.network.Pass != "" {
1158 uc.SendMessage(&irc.Message{
1159 Command: "PASS",
1160 Params: []string{uc.network.Pass},
1161 })
1162 }
1163
[92]1164 uc.SendMessage(&irc.Message{
[13]1165 Command: "NICK",
[69]1166 Params: []string{uc.nick},
[60]1167 })
1168 uc.SendMessage(&irc.Message{
[13]1169 Command: "USER",
[77]1170 Params: []string{uc.username, "0", "*", uc.realname},
[60]1171 })
[44]1172}
[13]1173
[197]1174func (uc *upstreamConn) runUntilRegistered() error {
1175 for !uc.registered {
[212]1176 msg, err := uc.ReadMessage()
[197]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
[263]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
[197]1195 return nil
1196}
1197
[95]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 })
[152]1245 case "message-tags":
1246 uc.tagsSupported = ok
[155]1247 case "labeled-response":
1248 uc.labelsSupported = ok
[193]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)
[95]1253 }
1254 return nil
1255}
1256
[165]1257func (uc *upstreamConn) readMessages(ch chan<- event) error {
[13]1258 for {
[210]1259 msg, err := uc.ReadMessage()
[13]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
[165]1266 ch <- eventUpstreamMessage{msg, uc}
[13]1267 }
1268
[45]1269 return nil
[13]1270}
[60]1271
[176]1272func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
[155]1273 if uc.labelsSupported {
1274 if msg.Tags == nil {
1275 msg.Tags = make(map[string]irc.TagValue)
1276 }
[176]1277 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
[161]1278 uc.nextLabelID++
[155]1279 }
1280 uc.SendMessage(msg)
1281}
[178]1282
1283// TODO: handle moving logs when a network name changes, when support for this is added
[215]1284func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
[178]1285 if uc.srv.LogPath == "" {
1286 return
1287 }
[215]1288
1289 ml, ok := uc.messageLoggers[entity]
1290 if !ok {
[248]1291 ml = newMessageLogger(uc.network, entity)
[215]1292 uc.messageLoggers[entity] = ml
[178]1293 }
1294
[215]1295 if err := ml.Append(msg); err != nil {
1296 uc.logger.Printf("failed to log message: %v", err)
[178]1297 }
1298}
[198]1299
[253]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
[245]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.
[239]1328func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
1329 if target != "" {
1330 uc.appendLog(target, msg)
1331 }
1332
[253]1333 uc.appendHistory(target, msg)
[227]1334
1335 uc.forEachDownstream(func(dc *downstreamConn) {
[238]1336 if dc != origin || dc.caps["echo-message"] {
[261]1337 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[238]1338 }
[227]1339 })
[226]1340}
1341
[198]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.