source: code/trunk/upstream.go@ 209

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

Fix writer goroutine races

Any SendMessage call after Close could potentially block forever if the
outgoing channel was filled up. Now the channel is drained before the
writer goroutine exits.

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