source: code/trunk/upstream.go@ 198

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

Auto away

Closes: https://todo.sr.ht/~emersion/soju/13

File size: 37.2 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
85 logger.Printf("connecting to TLS server at address %q", addr)
86 netConn, err := tls.Dial("tcp", addr, nil)
[33]87 if err != nil {
[77]88 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
[33]89 }
90
[67]91 setKeepAlive(netConn)
92
[102]93 outgoing := make(chan *irc.Message, 64)
[55]94 uc := &upstreamConn{
[177]95 network: network,
96 logger: logger,
97 net: netConn,
98 irc: irc.NewConn(netConn),
99 srv: network.user.srv,
100 user: network.user,
101 outgoing: outgoing,
[186]102 closed: make(chan struct{}),
[177]103 channels: make(map[string]*upstreamChannel),
104 caps: make(map[string]string),
105 batches: make(map[string]batch),
106 availableChannelTypes: stdChannelTypes,
107 availableChannelModes: stdChannelModes,
108 availableMemberships: stdMemberships,
109 pendingLISTDownstreamSet: make(map[uint64]struct{}),
[178]110 logs: make(map[string]entityLog),
[33]111 }
112
113 go func() {
[175]114 for {
115 var closed bool
116 select {
117 case msg := <-outgoing:
118 if uc.srv.Debug {
119 uc.logger.Printf("sent: %v", msg)
120 }
121 if err := uc.irc.WriteMessage(msg); err != nil {
122 uc.logger.Printf("failed to write message: %v", err)
123 }
124 case <-uc.closed:
125 closed = true
[64]126 }
[175]127 if closed {
128 break
[33]129 }
130 }
[55]131 if err := uc.net.Close(); err != nil {
132 uc.logger.Printf("failed to close connection: %v", err)
[45]133 } else {
[55]134 uc.logger.Printf("connection closed")
[45]135 }
[33]136 }()
137
[55]138 return uc, nil
[33]139}
140
[175]141func (uc *upstreamConn) isClosed() bool {
142 select {
143 case <-uc.closed:
144 return true
145 default:
146 return false
147 }
148}
149
[180]150// Close closes the connection. It is safe to call from any goroutine.
[55]151func (uc *upstreamConn) Close() error {
[175]152 if uc.isClosed() {
[33]153 return fmt.Errorf("upstream connection already closed")
154 }
[175]155 close(uc.closed)
[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.