Browse Source

Fix #448, #594

tags/v1.2.0-rc1
Shivaram Lingamneni 4 years ago
parent
commit
7ed27d4a42
9 changed files with 206 additions and 153 deletions
  1. 38
    1
      docs/MANUAL.md
  2. 32
    4
      irc/caps/set.go
  3. 7
    7
      irc/caps/set_test.go
  4. 0
    45
      irc/caps/values.go
  5. 2
    0
      irc/client.go
  6. 47
    7
      irc/config.go
  7. 29
    7
      irc/handlers.go
  8. 41
    76
      irc/server.go
  9. 10
    6
      oragono.yaml

+ 38
- 1
docs/MANUAL.md View File

32
     - History
32
     - History
33
     - IP cloaking
33
     - IP cloaking
34
 - Frequently Asked Questions
34
 - Frequently Asked Questions
35
+- IRC over TLS
35
 - Modes
36
 - Modes
36
     - User Modes
37
     - User Modes
37
     - Channel Modes
38
     - Channel Modes
342
 
343
 
343
 Otherwise, in the Oragono config file, you'll want to enable raw line logging by removing `-userinput -useroutput` under the `logging` section. Once you start up your server, connect, fail to oper and get disconnected, you'll see a bunch of input/output lines in Ora's log file. Remove your password from those logs and pass them our way.
344
 Otherwise, in the Oragono config file, you'll want to enable raw line logging by removing `-userinput -useroutput` under the `logging` section. Once you start up your server, connect, fail to oper and get disconnected, you'll see a bunch of input/output lines in Ora's log file. Remove your password from those logs and pass them our way.
344
 
345
 
346
+-------------------------------------------------------------------------------------------
347
+
348
+
349
+# IRC over TLS
350
+
351
+Traditionally, IRC used a plaintext protocol, typically on port 6667. Over time, a convention emerged to use this protocol inside SSL/TLS instead, typically on port 6697. As of now, we recommend that you make your server available *exclusively* via TLS, since allowing plaintext access can result in the disclosure of user data or passwords. While the default config file still exposes a public plaintext port for the benefit of legacy clients, it also contains instructions on how to disable it --- if at all possible, you should follow them!
352
+
353
+
345
 ## How do I use Let's Encrypt certificates?
354
 ## How do I use Let's Encrypt certificates?
346
 
355
 
347
-Every deployment's gonna be different, but you can use certificates from [Let's Encrypt](https://letsencrypt.org) without too much trouble. Here's some steps that should help get you on the right track:
356
+[Let's Encrypt](https://letsencrypt.org) is a widely recognized certificate authority that provides free certificates. Here's a quick-start guide for using those certificates with Oragono:
348
 
357
 
349
 1. Follow this [guidance](https://letsencrypt.org/getting-started/) from Let's Encrypt to create your certificates.
358
 1. Follow this [guidance](https://letsencrypt.org/getting-started/) from Let's Encrypt to create your certificates.
350
 2. You should now have a set of `pem` files, Mainly, we're interested in your `live/` Let's Encrypt directory (e.g. `/etc/letsencrypt/live/<site>/`).
359
 2. You should now have a set of `pem` files, Mainly, we're interested in your `live/` Let's Encrypt directory (e.g. `/etc/letsencrypt/live/<site>/`).
364
 On other platforms or with alternative ACME tools, you may need to use other steps or the specific files may be named differently.
373
 On other platforms or with alternative ACME tools, you may need to use other steps or the specific files may be named differently.
365
 
374
 
366
 
375
 
376
+## How can I "redirect" users from plaintext to TLS?
377
+
378
+The [STS specification](https://ircv3.net/specs/extensions/sts) can be used to redirect clients from plaintext to TLS automatically. If you set `server.sts.enabled` to `true`, clients with specific support for STS that connect in plaintext will disconnect and reconnect over TLS. To use STS, you must be using certificates issued by a generally recognized certificate authority, such as Let's Encrypt.
379
+
380
+Many clients do not have this support. However, you can designate port 6667 as an "STS-only" listener: any client that connects to such a listener will receive both the machine-readable STS policy and a human-readable message instructing them to reconnect over TLS, and will then be disconnected by the server before they can send or receive any chat data. Here is an example of how to configure this behavior:
381
+
382
+```yaml
383
+    listeners:
384
+        ":6667":
385
+            sts-only: true
386
+
387
+        # These are loopback-only plaintext listeners on port 6668:
388
+        "127.0.0.1:6668": # (loopback ipv4, localhost-only)
389
+        "[::1]:6668":     # (loopback ipv6, localhost-only)
390
+
391
+        ":6697":
392
+            tls:
393
+                key: tls.key
394
+                cert: tls.crt
395
+
396
+    sts:
397
+        enabled: true
398
+
399
+        # how long clients should be forced to use TLS for.
400
+        duration: 1mo2d5m
401
+```
402
+
403
+
367
 --------------------------------------------------------------------------------------------
404
 --------------------------------------------------------------------------------------------
368
 
405
 
369
 
406
 

+ 32
- 4
irc/caps/set.go View File

4
 package caps
4
 package caps
5
 
5
 
6
 import (
6
 import (
7
+	"bytes"
7
 	"sort"
8
 	"sort"
8
-	"strings"
9
 
9
 
10
 	"github.com/oragono/oragono/irc/utils"
10
 	"github.com/oragono/oragono/irc/utils"
11
 )
11
 )
13
 // Set holds a set of enabled capabilities.
13
 // Set holds a set of enabled capabilities.
14
 type Set [bitsetLen]uint32
14
 type Set [bitsetLen]uint32
15
 
15
 
16
+// Values holds capability values.
17
+type Values map[Capability]string
18
+
16
 // NewSet returns a new Set, with the given capabilities enabled.
19
 // NewSet returns a new Set, with the given capabilities enabled.
17
 func NewSet(capabs ...Capability) *Set {
20
 func NewSet(capabs ...Capability) *Set {
18
 	var newSet Set
21
 	var newSet Set
88
 	return utils.BitsetEmpty(s[:])
91
 	return utils.BitsetEmpty(s[:])
89
 }
92
 }
90
 
93
 
94
+const maxPayloadLength = 440
95
+
91
 // String returns all of our enabled capabilities as a string.
96
 // String returns all of our enabled capabilities as a string.
92
-func (s *Set) String(version Version, values *Values) string {
97
+func (s *Set) String(version Version, values Values) (result []string) {
93
 	var strs sort.StringSlice
98
 	var strs sort.StringSlice
94
 
99
 
95
 	var capab Capability
100
 	var capab Capability
101
 		}
106
 		}
102
 		capString := capab.Name()
107
 		capString := capab.Name()
103
 		if version == Cap302 {
108
 		if version == Cap302 {
104
-			val, exists := values.Get(capab)
109
+			val, exists := values[capab]
105
 			if exists {
110
 			if exists {
106
 				capString += "=" + val
111
 				capString += "=" + val
107
 			}
112
 			}
109
 		strs = append(strs, capString)
114
 		strs = append(strs, capString)
110
 	}
115
 	}
111
 
116
 
117
+	if len(strs) == 0 {
118
+		return []string{""}
119
+	}
120
+
112
 	// sort the cap string before we send it out
121
 	// sort the cap string before we send it out
113
 	sort.Sort(strs)
122
 	sort.Sort(strs)
114
 
123
 
115
-	return strings.Join(strs, " ")
124
+	var buf bytes.Buffer
125
+	for _, str := range strs {
126
+		tokenLen := len(str)
127
+		if buf.Len() != 0 {
128
+			tokenLen += 1
129
+		}
130
+		if maxPayloadLength < buf.Len()+tokenLen {
131
+			result = append(result, buf.String())
132
+			buf.Reset()
133
+		}
134
+		if buf.Len() != 0 {
135
+			buf.WriteByte(' ')
136
+		}
137
+		buf.WriteString(str)
138
+	}
139
+	if buf.Len() != 0 {
140
+		result = append(result, buf.String())
141
+	}
142
+
143
+	return
116
 }
144
 }

+ 7
- 7
irc/caps/set_test.go View File

44
 	}
44
 	}
45
 
45
 
46
 	// test String()
46
 	// test String()
47
-	values := NewValues()
48
-	values.Set(InviteNotify, "invitemepls")
47
+	values := make(Values)
48
+	values[InviteNotify] = "invitemepls"
49
 
49
 
50
 	actualCap301ValuesString := s1.String(Cap301, values)
50
 	actualCap301ValuesString := s1.String(Cap301, values)
51
-	expectedCap301ValuesString := "invite-notify userhost-in-names"
52
-	if actualCap301ValuesString != expectedCap301ValuesString {
53
-		t.Errorf("Generated Cap301 values string [%s] did not match expected values string [%s]", actualCap301ValuesString, expectedCap301ValuesString)
51
+	expectedCap301ValuesString := []string{"invite-notify userhost-in-names"}
52
+	if !reflect.DeepEqual(actualCap301ValuesString, expectedCap301ValuesString) {
53
+		t.Errorf("Generated Cap301 values string [%v] did not match expected values string [%v]", actualCap301ValuesString, expectedCap301ValuesString)
54
 	}
54
 	}
55
 
55
 
56
 	actualCap302ValuesString := s1.String(Cap302, values)
56
 	actualCap302ValuesString := s1.String(Cap302, values)
57
-	expectedCap302ValuesString := "invite-notify=invitemepls userhost-in-names"
58
-	if actualCap302ValuesString != expectedCap302ValuesString {
57
+	expectedCap302ValuesString := []string{"invite-notify=invitemepls userhost-in-names"}
58
+	if !reflect.DeepEqual(actualCap302ValuesString, expectedCap302ValuesString) {
59
 		t.Errorf("Generated Cap302 values string [%s] did not match expected values string [%s]", actualCap302ValuesString, expectedCap302ValuesString)
59
 		t.Errorf("Generated Cap302 values string [%s] did not match expected values string [%s]", actualCap302ValuesString, expectedCap302ValuesString)
60
 	}
60
 	}
61
 }
61
 }

+ 0
- 45
irc/caps/values.go View File

1
-// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
2
-// released under the MIT license
3
-
4
-package caps
5
-
6
-import "sync"
7
-
8
-// Values holds capability values.
9
-type Values struct {
10
-	sync.RWMutex
11
-	// values holds our actual capability values.
12
-	values map[Capability]string
13
-}
14
-
15
-// NewValues returns a new Values.
16
-func NewValues() *Values {
17
-	return &Values{
18
-		values: make(map[Capability]string),
19
-	}
20
-}
21
-
22
-// Set sets the value for the given capability.
23
-func (v *Values) Set(capab Capability, value string) {
24
-	v.Lock()
25
-	defer v.Unlock()
26
-
27
-	v.values[capab] = value
28
-}
29
-
30
-// Unset removes the value for the given capability, if it exists.
31
-func (v *Values) Unset(capab Capability) {
32
-	v.Lock()
33
-	defer v.Unlock()
34
-
35
-	delete(v.values, capab)
36
-}
37
-
38
-// Get returns the value of the given capability, and whether one exists.
39
-func (v *Values) Get(capab Capability) (string, bool) {
40
-	v.RLock()
41
-	defer v.RUnlock()
42
-
43
-	value, exists := v.values[capab]
44
-	return value, exists
45
-}

+ 2
- 0
irc/client.go View File

58
 	flags              modes.ModeSet
58
 	flags              modes.ModeSet
59
 	hostname           string
59
 	hostname           string
60
 	invitedTo          map[string]bool
60
 	invitedTo          map[string]bool
61
+	isSTSOnly          bool
61
 	isTor              bool
62
 	isTor              bool
62
 	languages          []string
63
 	languages          []string
63
 	loginThrottle      connection_limits.GenericThrottle
64
 	loginThrottle      connection_limits.GenericThrottle
220
 		atime:     now,
221
 		atime:     now,
221
 		channels:  make(ChannelSet),
222
 		channels:  make(ChannelSet),
222
 		ctime:     now,
223
 		ctime:     now,
224
+		isSTSOnly: conn.Config.IsSTSOnly,
223
 		isTor:     conn.Config.IsTor,
225
 		isTor:     conn.Config.IsTor,
224
 		languages: server.Languages().Default(),
226
 		languages: server.Languages().Default(),
225
 		loginThrottle: connection_limits.GenericThrottle{
227
 		loginThrottle: connection_limits.GenericThrottle{

+ 47
- 7
irc/config.go View File

14
 	"os"
14
 	"os"
15
 	"regexp"
15
 	"regexp"
16
 	"sort"
16
 	"sort"
17
+	"strconv"
17
 	"strings"
18
 	"strings"
18
 	"time"
19
 	"time"
19
 
20
 
20
 	"code.cloudfoundry.org/bytefmt"
21
 	"code.cloudfoundry.org/bytefmt"
22
+	"github.com/oragono/oragono/irc/caps"
21
 	"github.com/oragono/oragono/irc/cloaks"
23
 	"github.com/oragono/oragono/irc/cloaks"
22
 	"github.com/oragono/oragono/irc/connection_limits"
24
 	"github.com/oragono/oragono/irc/connection_limits"
23
 	"github.com/oragono/oragono/irc/custime"
25
 	"github.com/oragono/oragono/irc/custime"
43
 
45
 
44
 // This is the YAML-deserializable type of the value of the `Server.Listeners` map
46
 // This is the YAML-deserializable type of the value of the `Server.Listeners` map
45
 type listenerConfigBlock struct {
47
 type listenerConfigBlock struct {
46
-	TLS TLSListenConfig
47
-	Tor bool
48
+	TLS     TLSListenConfig
49
+	Tor     bool
50
+	STSOnly bool `yaml:"sts-only"`
48
 }
51
 }
49
 
52
 
50
 // listenerConfig is the config governing a particular listener (bound address),
53
 // listenerConfig is the config governing a particular listener (bound address),
52
 type listenerConfig struct {
55
 type listenerConfig struct {
53
 	TLSConfig *tls.Config
56
 	TLSConfig *tls.Config
54
 	IsTor     bool
57
 	IsTor     bool
58
+	IsSTSOnly bool
55
 }
59
 }
56
 
60
 
57
 type AccountConfig struct {
61
 type AccountConfig struct {
235
 	DurationString string        `yaml:"duration"`
239
 	DurationString string        `yaml:"duration"`
236
 	Port           int
240
 	Port           int
237
 	Preload        bool
241
 	Preload        bool
242
+	STSOnlyBanner  string `yaml:"sts-only-banner"`
243
+	bannerLines    []string
238
 }
244
 }
239
 
245
 
240
 // Value returns the STS value to advertise in CAP
246
 // Value returns the STS value to advertise in CAP
306
 		ConnectionLimiter   connection_limits.LimiterConfig   `yaml:"connection-limits"`
312
 		ConnectionLimiter   connection_limits.LimiterConfig   `yaml:"connection-limits"`
307
 		ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
313
 		ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
308
 		Cloaks              cloaks.CloakConfig                `yaml:"ip-cloaking"`
314
 		Cloaks              cloaks.CloakConfig                `yaml:"ip-cloaking"`
315
+		supportedCaps       *caps.Set
316
+		capValues           caps.Values
309
 	}
317
 	}
310
 
318
 
311
 	Languages struct {
319
 	Languages struct {
511
 		for addr, block := range conf.Server.Listeners {
519
 		for addr, block := range conf.Server.Listeners {
512
 			var lconf listenerConfig
520
 			var lconf listenerConfig
513
 			lconf.IsTor = block.Tor
521
 			lconf.IsTor = block.Tor
522
+			lconf.IsSTSOnly = block.STSOnly
523
+			if lconf.IsSTSOnly && !conf.Server.STS.Enabled {
524
+				return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr)
525
+			}
514
 			if block.TLS.Cert != "" {
526
 			if block.TLS.Cert != "" {
515
 				tlsConfig, err := loadTlsConfig(block.TLS)
527
 				tlsConfig, err := loadTlsConfig(block.TLS)
516
 				if err != nil {
528
 				if err != nil {
592
 	if config.Limits.RegistrationMessages == 0 {
604
 	if config.Limits.RegistrationMessages == 0 {
593
 		config.Limits.RegistrationMessages = 1024
605
 		config.Limits.RegistrationMessages = 1024
594
 	}
606
 	}
607
+
608
+	config.Server.supportedCaps = caps.NewCompleteSet()
609
+	config.Server.capValues = make(caps.Values)
610
+
611
+	err = config.prepareListeners()
612
+	if err != nil {
613
+		return nil, fmt.Errorf("failed to prepare listeners: %v", err)
614
+	}
615
+
595
 	if config.Server.STS.Enabled {
616
 	if config.Server.STS.Enabled {
596
 		config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString)
617
 		config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString)
597
 		if err != nil {
618
 		if err != nil {
600
 		if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
621
 		if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
601
 			return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
622
 			return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
602
 		}
623
 		}
624
+		if config.Server.STS.STSOnlyBanner != "" {
625
+			config.Server.STS.bannerLines = utils.WordWrap(config.Server.STS.STSOnlyBanner, 400)
626
+		} else {
627
+			config.Server.STS.bannerLines = []string{fmt.Sprintf("This server is only accessible over TLS. Please reconnect using TLS on port %d.", config.Server.STS.Port)}
628
+		}
629
+	} else {
630
+		config.Server.supportedCaps.Disable(caps.STS)
631
+		config.Server.STS.Duration = 0
603
 	}
632
 	}
633
+	// set this even if STS is disabled
634
+	config.Server.capValues[caps.STS] = config.Server.STS.Value()
635
+
604
 	if config.Server.ConnectionThrottler.Enabled {
636
 	if config.Server.ConnectionThrottler.Enabled {
605
 		config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString)
637
 		config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString)
606
 		if err != nil {
638
 		if err != nil {
626
 		newWebIRC = append(newWebIRC, webirc)
658
 		newWebIRC = append(newWebIRC, webirc)
627
 	}
659
 	}
628
 	config.Server.WebIRC = newWebIRC
660
 	config.Server.WebIRC = newWebIRC
661
+
629
 	// process limits
662
 	// process limits
630
 	if config.Limits.LineLen.Rest < 512 {
663
 	if config.Limits.LineLen.Rest < 512 {
631
 		config.Limits.LineLen.Rest = 512
664
 		config.Limits.LineLen.Rest = 512
632
 	}
665
 	}
666
+	if config.Limits.LineLen.Rest == 512 {
667
+		config.Server.supportedCaps.Disable(caps.MaxLine)
668
+	} else {
669
+		config.Server.capValues[caps.MaxLine] = strconv.Itoa(config.Limits.LineLen.Rest)
670
+	}
671
+
633
 	var newLogConfigs []logger.LoggingConfig
672
 	var newLogConfigs []logger.LoggingConfig
634
 	for _, logConfig := range config.Logging {
673
 	for _, logConfig := range config.Logging {
635
 		// methods
674
 		// methods
713
 		config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
752
 		config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
714
 	}
753
 	}
715
 
754
 
755
+	config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
756
+	if !config.Accounts.AuthenticationEnabled {
757
+		config.Server.supportedCaps.Disable(caps.SASL)
758
+	}
759
+
716
 	maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
760
 	maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
717
 	if err != nil {
761
 	if err != nil {
718
 		return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
762
 		return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
723
 	if err != nil {
767
 	if err != nil {
724
 		return nil, fmt.Errorf("Could not load languages: %s", err.Error())
768
 		return nil, fmt.Errorf("Could not load languages: %s", err.Error())
725
 	}
769
 	}
770
+	config.Server.capValues[caps.Languages] = config.languageManager.CapValue()
726
 
771
 
727
 	// RecoverFromErrors defaults to true
772
 	// RecoverFromErrors defaults to true
728
 	if config.Debug.RecoverFromErrors != nil {
773
 	if config.Debug.RecoverFromErrors != nil {
798
 		}
843
 		}
799
 	}
844
 	}
800
 
845
 
801
-	err = config.prepareListeners()
802
-	if err != nil {
803
-		return nil, fmt.Errorf("failed to prepare listeners: %v", err)
804
-	}
805
-
806
 	return config, nil
846
 	return config, nil
807
 }
847
 }

+ 29
- 7
irc/handlers.go View File

301
 	config := server.Config()
301
 	config := server.Config()
302
 	details := client.Details()
302
 	details := client.Details()
303
 
303
 
304
+	if client.isSTSOnly {
305
+		rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
306
+		return false
307
+	}
308
+
304
 	if details.account != "" {
309
 	if details.account != "" {
305
 		rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
310
 		rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
306
 		return false
311
 		return false
535
 	toRemove := caps.NewSet()
540
 	toRemove := caps.NewSet()
536
 	var capString string
541
 	var capString string
537
 
542
 
543
+	config := server.Config()
544
+	supportedCaps := config.Server.supportedCaps
545
+	if client.isSTSOnly {
546
+		supportedCaps = stsOnlyCaps
547
+	}
548
+
538
 	badCaps := false
549
 	badCaps := false
539
 	if len(msg.Params) > 1 {
550
 	if len(msg.Params) > 1 {
540
 		capString = msg.Params[1]
551
 		capString = msg.Params[1]
546
 				remove = true
557
 				remove = true
547
 			}
558
 			}
548
 			capab, err := caps.NameToCapability(str)
559
 			capab, err := caps.NameToCapability(str)
549
-			if err != nil || (!remove && !SupportedCapabilities.Has(capab)) {
560
+			if err != nil || (!remove && !supportedCaps.Has(capab)) {
550
 				badCaps = true
561
 				badCaps = true
551
 			} else if !remove {
562
 			} else if !remove {
552
 				toAdd.Enable(capab)
563
 				toAdd.Enable(capab)
556
 		}
567
 		}
557
 	}
568
 	}
558
 
569
 
570
+	sendCapLines := func(cset *caps.Set, values caps.Values) {
571
+		version := rb.session.capVersion
572
+		capLines := cset.String(version, values)
573
+		// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
574
+		// the server.name source:
575
+		for i, capStr := range capLines {
576
+			if version == caps.Cap302 && i < len(capLines)-1 {
577
+				rb.Add(nil, server.name, "CAP", details.nick, subCommand, "*", capStr)
578
+			} else {
579
+				rb.Add(nil, server.name, "CAP", details.nick, subCommand, capStr)
580
+			}
581
+		}
582
+	}
583
+
559
 	switch subCommand {
584
 	switch subCommand {
560
 	case "LS":
585
 	case "LS":
561
 		if !client.registered {
586
 		if !client.registered {
568
 				rb.session.capVersion = newVersion
593
 				rb.session.capVersion = newVersion
569
 			}
594
 			}
570
 		}
595
 		}
571
-		// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
572
-		// the server.name source... otherwise it doesn't respond to the CAP message with
573
-		// anything and just hangs on connection.
574
-		//TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
575
-		rb.Add(nil, server.name, "CAP", details.nick, subCommand, SupportedCapabilities.String(rb.session.capVersion, CapValues))
596
+		sendCapLines(supportedCaps, config.Server.capValues)
576
 
597
 
577
 	case "LIST":
598
 	case "LIST":
578
-		rb.Add(nil, server.name, "CAP", details.nick, subCommand, rb.session.capabilities.String(caps.Cap301, CapValues)) // values not sent on LIST so force 3.1
599
+		// values not sent on LIST
600
+		sendCapLines(&rb.session.capabilities, nil)
579
 
601
 
580
 	case "REQ":
602
 	case "REQ":
581
 		if !client.registered {
603
 		if !client.registered {

+ 41
- 76
irc/server.go View File

39
 	// supportedChannelModesString acts as a cache for when we introduce users
39
 	// supportedChannelModesString acts as a cache for when we introduce users
40
 	supportedChannelModesString = modes.SupportedChannelModes.String()
40
 	supportedChannelModesString = modes.SupportedChannelModes.String()
41
 
41
 
42
-	// SupportedCapabilities are the caps we advertise.
43
-	// MaxLine, SASL and STS may be unset during server startup / rehash.
44
-	SupportedCapabilities = caps.NewCompleteSet()
45
-
46
-	// CapValues are the actual values we advertise to v3.2 clients.
47
-	// actual values are set during server startup.
48
-	CapValues = caps.NewValues()
42
+	// whitelist of caps to serve on the STS-only listener. In particular,
43
+	// never advertise SASL, to discourage people from sending their passwords:
44
+	stsOnlyCaps = caps.NewSet(caps.STS, caps.MessageTags, caps.ServerTime, caps.LabeledResponse, caps.Nope)
49
 )
45
 )
50
 
46
 
51
 // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
47
 // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
340
 		return
336
 		return
341
 	}
337
 	}
342
 
338
 
339
+	if c.isSTSOnly {
340
+		server.playRegistrationBurst(session)
341
+		return true
342
+	}
343
+
343
 	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
344
 	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
344
 	// before completing the other registration commands
345
 	// before completing the other registration commands
345
 	authOutcome := c.isAuthorized(server.Config())
346
 	authOutcome := c.isAuthorized(server.Config())
407
 	//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
408
 	//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
408
 	session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
409
 	session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
409
 
410
 
411
+	if c.isSTSOnly {
412
+		for _, line := range server.Config().Server.STS.bannerLines {
413
+			c.Notice(line)
414
+		}
415
+		return
416
+	}
417
+
410
 	rb := NewResponseBuffer(session)
418
 	rb := NewResponseBuffer(session)
411
 	server.RplISupport(c, rb)
419
 	server.RplISupport(c, rb)
412
 	server.Lusers(c, rb)
420
 	server.Lusers(c, rb)
623
 	server.logger.Debug("server", "Regenerating HELP indexes for new languages")
631
 	server.logger.Debug("server", "Regenerating HELP indexes for new languages")
624
 	server.helpIndexManager.GenerateIndices(config.languageManager)
632
 	server.helpIndexManager.GenerateIndices(config.languageManager)
625
 
633
 
626
-	currentLanguageValue, _ := CapValues.Get(caps.Languages)
627
-	newLanguageValue := config.languageManager.CapValue()
628
-	if currentLanguageValue != newLanguageValue {
634
+	if oldConfig != nil && config.Server.capValues[caps.Languages] != oldConfig.Server.capValues[caps.Languages] {
629
 		updatedCaps.Add(caps.Languages)
635
 		updatedCaps.Add(caps.Languages)
630
-		CapValues.Set(caps.Languages, newLanguageValue)
631
 	}
636
 	}
632
 
637
 
633
 	// SASL
638
 	// SASL
634
 	authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled
639
 	authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled
635
 	if config.Accounts.AuthenticationEnabled && (oldConfig == nil || !authPreviouslyEnabled) {
640
 	if config.Accounts.AuthenticationEnabled && (oldConfig == nil || !authPreviouslyEnabled) {
636
 		// enabling SASL
641
 		// enabling SASL
637
-		SupportedCapabilities.Enable(caps.SASL)
638
-		CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
639
 		addedCaps.Add(caps.SASL)
642
 		addedCaps.Add(caps.SASL)
640
 	} else if !config.Accounts.AuthenticationEnabled && (oldConfig == nil || authPreviouslyEnabled) {
643
 	} else if !config.Accounts.AuthenticationEnabled && (oldConfig == nil || authPreviouslyEnabled) {
641
 		// disabling SASL
644
 		// disabling SASL
642
-		SupportedCapabilities.Disable(caps.SASL)
643
 		removedCaps.Add(caps.SASL)
645
 		removedCaps.Add(caps.SASL)
644
 	}
646
 	}
645
 
647
 
661
 		server.channels.loadRegisteredChannels()
663
 		server.channels.loadRegisteredChannels()
662
 	}
664
 	}
663
 
665
 
664
-	// MaxLine
665
-	if config.Limits.LineLen.Rest != 512 {
666
-		SupportedCapabilities.Enable(caps.MaxLine)
667
-		value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
668
-		CapValues.Set(caps.MaxLine, value)
669
-	} else {
670
-		SupportedCapabilities.Disable(caps.MaxLine)
671
-	}
672
-
673
 	// STS
666
 	// STS
674
 	stsPreviouslyEnabled := oldConfig != nil && oldConfig.Server.STS.Enabled
667
 	stsPreviouslyEnabled := oldConfig != nil && oldConfig.Server.STS.Enabled
675
-	stsValue := config.Server.STS.Value()
676
-	stsDisabledByRehash := false
677
-	stsCurrentCapValue, _ := CapValues.Get(caps.STS)
668
+	stsValue := config.Server.capValues[caps.STS]
669
+	stsCurrentCapValue := ""
670
+	if oldConfig != nil {
671
+		stsCurrentCapValue = oldConfig.Server.capValues[caps.STS]
672
+	}
678
 	server.logger.Debug("server", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled))
673
 	server.logger.Debug("server", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled))
679
-	if config.Server.STS.Enabled {
680
-		// enabling STS
681
-		SupportedCapabilities.Enable(caps.STS)
682
-		if !stsPreviouslyEnabled {
683
-			addedCaps.Add(caps.STS)
684
-			CapValues.Set(caps.STS, stsValue)
685
-		} else if stsValue != stsCurrentCapValue {
686
-			// STS policy updated
687
-			CapValues.Set(caps.STS, stsValue)
688
-			updatedCaps.Add(caps.STS)
689
-		}
690
-	} else {
691
-		// disabling STS
692
-		SupportedCapabilities.Disable(caps.STS)
693
-		if stsPreviouslyEnabled {
694
-			removedCaps.Add(caps.STS)
695
-			stsDisabledByRehash = true
696
-		}
674
+	if (config.Server.STS.Enabled != stsPreviouslyEnabled) || (stsValue != stsCurrentCapValue) {
675
+		// XXX: STS is always removed by CAP NEW sts=duration=0, not CAP DEL
676
+		addedCaps.Add(caps.STS)
697
 	}
677
 	}
698
 
678
 
699
 	// resize history buffers as needed
679
 	// resize history buffers as needed
708
 
688
 
709
 	// burst new and removed caps
689
 	// burst new and removed caps
710
 	var capBurstSessions []*Session
690
 	var capBurstSessions []*Session
711
-	added := make(map[caps.Version]string)
712
-	var removed string
691
+	added := make(map[caps.Version][]string)
692
+	var removed []string
713
 
693
 
714
 	// updated caps get DEL'd and then NEW'd
694
 	// updated caps get DEL'd and then NEW'd
715
 	// so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
695
 	// so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
716
-	server.logger.Debug("server", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues))
696
+	server.logger.Debug("server", "Updated Caps", strings.Join(updatedCaps.String(caps.Cap301, config.Server.capValues), " "))
717
 	addedCaps.Union(updatedCaps)
697
 	addedCaps.Union(updatedCaps)
718
 	removedCaps.Union(updatedCaps)
698
 	removedCaps.Union(updatedCaps)
719
 
699
 
720
 	if !addedCaps.Empty() || !removedCaps.Empty() {
700
 	if !addedCaps.Empty() || !removedCaps.Empty() {
721
 		capBurstSessions = server.clients.AllWithCapsNotify()
701
 		capBurstSessions = server.clients.AllWithCapsNotify()
722
 
702
 
723
-		added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
724
-		added[caps.Cap302] = addedCaps.String(caps.Cap302, CapValues)
703
+		added[caps.Cap301] = addedCaps.String(caps.Cap301, config.Server.capValues)
704
+		added[caps.Cap302] = addedCaps.String(caps.Cap302, config.Server.capValues)
725
 		// removed never has values, so we leave it as Cap301
705
 		// removed never has values, so we leave it as Cap301
726
-		removed = removedCaps.String(caps.Cap301, CapValues)
706
+		removed = removedCaps.String(caps.Cap301, config.Server.capValues)
727
 	}
707
 	}
728
 
708
 
729
 	for _, sSession := range capBurstSessions {
709
 	for _, sSession := range capBurstSessions {
730
-		if stsDisabledByRehash {
731
-			// remove STS policy
732
-			//TODO(dan): this is an ugly hack. we can write this better.
733
-			stsPolicy := "sts=duration=0"
734
-			if !addedCaps.Empty() {
735
-				added[caps.Cap302] = added[caps.Cap302] + " " + stsPolicy
736
-			} else {
737
-				addedCaps.Enable(caps.STS)
738
-				added[caps.Cap302] = stsPolicy
739
-			}
740
-		}
741
 		// DEL caps and then send NEW ones so that updated caps get removed/added correctly
710
 		// DEL caps and then send NEW ones so that updated caps get removed/added correctly
742
 		if !removedCaps.Empty() {
711
 		if !removedCaps.Empty() {
743
-			sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "DEL", removed)
712
+			for _, capStr := range removed {
713
+				sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "DEL", capStr)
714
+			}
744
 		}
715
 		}
745
 		if !addedCaps.Empty() {
716
 		if !addedCaps.Empty() {
746
-			sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "NEW", added[sSession.capVersion])
717
+			for _, capStr := range added[sSession.capVersion] {
718
+				sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "NEW", capStr)
719
+			}
747
 		}
720
 		}
748
 	}
721
 	}
749
 
722
 
905
 		}
878
 		}
906
 	}
879
 	}
907
 
880
 
881
+	publicPlaintextListener := ""
908
 	// create new listeners that were not previously configured
882
 	// create new listeners that were not previously configured
909
-	numTlsListeners := 0
910
-	hasStandardTlsListener := false
911
 	for newAddr, newConfig := range config.Server.trueListeners {
883
 	for newAddr, newConfig := range config.Server.trueListeners {
912
-		if newConfig.TLSConfig != nil {
913
-			numTlsListeners += 1
914
-			if strings.HasSuffix(newAddr, ":6697") {
915
-				hasStandardTlsListener = true
916
-			}
884
+		if strings.HasPrefix(newAddr, ":") && !newConfig.IsTor && !newConfig.IsSTSOnly && newConfig.TLSConfig == nil {
885
+			publicPlaintextListener = newAddr
917
 		}
886
 		}
918
 		_, exists := server.listeners[newAddr]
887
 		_, exists := server.listeners[newAddr]
919
 		if !exists {
888
 		if !exists {
929
 		}
898
 		}
930
 	}
899
 	}
931
 
900
 
932
-	if numTlsListeners == 0 {
933
-		server.logger.Warning("server", "You are not exposing an SSL/TLS listening port. You should expose at least one port (typically 6697) to accept TLS connections")
934
-	}
935
-
936
-	if !hasStandardTlsListener {
937
-		server.logger.Warning("server", "Port 6697 is the standard TLS port for IRC. You should (also) expose port 6697 as a TLS port to ensure clients can connect securely")
901
+	if publicPlaintextListener != "" {
902
+		server.logger.Warning("listeners", fmt.Sprintf("Your server is configured with public plaintext listener %s. Consider disabling it for improved security and privacy.", publicPlaintextListener))
938
 	}
903
 	}
939
 
904
 
940
 	return
905
 	return

+ 10
- 6
oragono.yaml View File

15
         # The standard plaintext port for IRC is 6667. This will listen on all interfaces:
15
         # The standard plaintext port for IRC is 6667. This will listen on all interfaces:
16
         ":6667":
16
         ":6667":
17
 
17
 
18
+        # Allowing plaintext over the public Internet poses security and privacy issues,
19
+        # so if possible, we recommend that you comment out the above line and replace
20
+        # it with these two, which listen only on local interfaces:
21
+        # "127.0.0.1:6667": # (loopback ipv4, localhost-only)
22
+        # "[::1]:6667":     # (loopback ipv6, localhost-only)
23
+        # Alternately, if you have a TLS certificate issued by a recognized CA,
24
+        # you can configure port 6667 as an STS-only listener that only serves
25
+        # "redirects" to the TLS port, but doesn't allow chat. See the manual
26
+        # for details.
27
+
18
         # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
28
         # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
19
         ":6697":
29
         ":6697":
20
             tls:
30
             tls:
21
                 key: tls.key
31
                 key: tls.key
22
                 cert: tls.crt
32
                 cert: tls.crt
23
 
33
 
24
-        # Since using plaintext over the public Internet poses security and privacy issues,
25
-        # you may wish to use plaintext only on local interfaces. To do so, comment out
26
-        # the `":6667":` line, then uncomment these two lines:
27
-        # "127.0.0.1:6667": # (loopback ipv4, localhost-only)
28
-        # "[::1]:6667":     # (loopback ipv6, localhost-only)
29
-
30
         # Example of a Unix domain socket for proxying:
34
         # Example of a Unix domain socket for proxying:
31
         # "/tmp/oragono_sock":
35
         # "/tmp/oragono_sock":
32
 
36
 

Loading…
Cancel
Save