source: code/trunk/upstream.go@ 205

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

Set write deadlines

References: https://todo.sr.ht/~emersion/soju/26

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