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,6 +32,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
32 32
     - History
33 33
     - IP cloaking
34 34
 - Frequently Asked Questions
35
+- IRC over TLS
35 36
 - Modes
36 37
     - User Modes
37 38
     - Channel Modes
@@ -342,9 +343,17 @@ If you're familiar with getting this output through your client (e.g. in weechat
342 343
 
343 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 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 358
 1. Follow this [guidance](https://letsencrypt.org/getting-started/) from Let's Encrypt to create your certificates.
350 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,6 +373,34 @@ The main issues you'll run into are going to be permissions issues. This is beca
364 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,8 +4,8 @@
4 4
 package caps
5 5
 
6 6
 import (
7
+	"bytes"
7 8
 	"sort"
8
-	"strings"
9 9
 
10 10
 	"github.com/oragono/oragono/irc/utils"
11 11
 )
@@ -13,6 +13,9 @@ import (
13 13
 // Set holds a set of enabled capabilities.
14 14
 type Set [bitsetLen]uint32
15 15
 
16
+// Values holds capability values.
17
+type Values map[Capability]string
18
+
16 19
 // NewSet returns a new Set, with the given capabilities enabled.
17 20
 func NewSet(capabs ...Capability) *Set {
18 21
 	var newSet Set
@@ -88,8 +91,10 @@ func (s *Set) Empty() bool {
88 91
 	return utils.BitsetEmpty(s[:])
89 92
 }
90 93
 
94
+const maxPayloadLength = 440
95
+
91 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 98
 	var strs sort.StringSlice
94 99
 
95 100
 	var capab Capability
@@ -101,7 +106,7 @@ func (s *Set) String(version Version, values *Values) string {
101 106
 		}
102 107
 		capString := capab.Name()
103 108
 		if version == Cap302 {
104
-			val, exists := values.Get(capab)
109
+			val, exists := values[capab]
105 110
 			if exists {
106 111
 				capString += "=" + val
107 112
 			}
@@ -109,8 +114,31 @@ func (s *Set) String(version Version, values *Values) string {
109 114
 		strs = append(strs, capString)
110 115
 	}
111 116
 
117
+	if len(strs) == 0 {
118
+		return []string{""}
119
+	}
120
+
112 121
 	// sort the cap string before we send it out
113 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,18 +44,18 @@ func TestSets(t *testing.T) {
44 44
 	}
45 45
 
46 46
 	// test String()
47
-	values := NewValues()
48
-	values.Set(InviteNotify, "invitemepls")
47
+	values := make(Values)
48
+	values[InviteNotify] = "invitemepls"
49 49
 
50 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 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 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,45 +0,0 @@
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,6 +58,7 @@ type Client struct {
58 58
 	flags              modes.ModeSet
59 59
 	hostname           string
60 60
 	invitedTo          map[string]bool
61
+	isSTSOnly          bool
61 62
 	isTor              bool
62 63
 	languages          []string
63 64
 	loginThrottle      connection_limits.GenericThrottle
@@ -220,6 +221,7 @@ func (server *Server) RunClient(conn clientConn) {
220 221
 		atime:     now,
221 222
 		channels:  make(ChannelSet),
222 223
 		ctime:     now,
224
+		isSTSOnly: conn.Config.IsSTSOnly,
223 225
 		isTor:     conn.Config.IsTor,
224 226
 		languages: server.Languages().Default(),
225 227
 		loginThrottle: connection_limits.GenericThrottle{

+ 47
- 7
irc/config.go View File

@@ -14,10 +14,12 @@ import (
14 14
 	"os"
15 15
 	"regexp"
16 16
 	"sort"
17
+	"strconv"
17 18
 	"strings"
18 19
 	"time"
19 20
 
20 21
 	"code.cloudfoundry.org/bytefmt"
22
+	"github.com/oragono/oragono/irc/caps"
21 23
 	"github.com/oragono/oragono/irc/cloaks"
22 24
 	"github.com/oragono/oragono/irc/connection_limits"
23 25
 	"github.com/oragono/oragono/irc/custime"
@@ -43,8 +45,9 @@ type TLSListenConfig struct {
43 45
 
44 46
 // This is the YAML-deserializable type of the value of the `Server.Listeners` map
45 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 53
 // listenerConfig is the config governing a particular listener (bound address),
@@ -52,6 +55,7 @@ type listenerConfigBlock struct {
52 55
 type listenerConfig struct {
53 56
 	TLSConfig *tls.Config
54 57
 	IsTor     bool
58
+	IsSTSOnly bool
55 59
 }
56 60
 
57 61
 type AccountConfig struct {
@@ -235,6 +239,8 @@ type STSConfig struct {
235 239
 	DurationString string        `yaml:"duration"`
236 240
 	Port           int
237 241
 	Preload        bool
242
+	STSOnlyBanner  string `yaml:"sts-only-banner"`
243
+	bannerLines    []string
238 244
 }
239 245
 
240 246
 // Value returns the STS value to advertise in CAP
@@ -306,6 +312,8 @@ type Config struct {
306 312
 		ConnectionLimiter   connection_limits.LimiterConfig   `yaml:"connection-limits"`
307 313
 		ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
308 314
 		Cloaks              cloaks.CloakConfig                `yaml:"ip-cloaking"`
315
+		supportedCaps       *caps.Set
316
+		capValues           caps.Values
309 317
 	}
310 318
 
311 319
 	Languages struct {
@@ -511,6 +519,10 @@ func (conf *Config) prepareListeners() (err error) {
511 519
 		for addr, block := range conf.Server.Listeners {
512 520
 			var lconf listenerConfig
513 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 526
 			if block.TLS.Cert != "" {
515 527
 				tlsConfig, err := loadTlsConfig(block.TLS)
516 528
 				if err != nil {
@@ -592,6 +604,15 @@ func LoadConfig(filename string) (config *Config, err error) {
592 604
 	if config.Limits.RegistrationMessages == 0 {
593 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 616
 	if config.Server.STS.Enabled {
596 617
 		config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString)
597 618
 		if err != nil {
@@ -600,7 +621,18 @@ func LoadConfig(filename string) (config *Config, err error) {
600 621
 		if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
601 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 636
 	if config.Server.ConnectionThrottler.Enabled {
605 637
 		config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString)
606 638
 		if err != nil {
@@ -626,10 +658,17 @@ func LoadConfig(filename string) (config *Config, err error) {
626 658
 		newWebIRC = append(newWebIRC, webirc)
627 659
 	}
628 660
 	config.Server.WebIRC = newWebIRC
661
+
629 662
 	// process limits
630 663
 	if config.Limits.LineLen.Rest < 512 {
631 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 672
 	var newLogConfigs []logger.LoggingConfig
634 673
 	for _, logConfig := range config.Logging {
635 674
 		// methods
@@ -713,6 +752,11 @@ func LoadConfig(filename string) (config *Config, err error) {
713 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 760
 	maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
717 761
 	if err != nil {
718 762
 		return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
@@ -723,6 +767,7 @@ func LoadConfig(filename string) (config *Config, err error) {
723 767
 	if err != nil {
724 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 772
 	// RecoverFromErrors defaults to true
728 773
 	if config.Debug.RecoverFromErrors != nil {
@@ -798,10 +843,5 @@ func LoadConfig(filename string) (config *Config, err error) {
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 846
 	return config, nil
807 847
 }

+ 29
- 7
irc/handlers.go View File

@@ -301,6 +301,11 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
301 301
 	config := server.Config()
302 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 309
 	if details.account != "" {
305 310
 		rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
306 311
 		return false
@@ -535,6 +540,12 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
535 540
 	toRemove := caps.NewSet()
536 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 549
 	badCaps := false
539 550
 	if len(msg.Params) > 1 {
540 551
 		capString = msg.Params[1]
@@ -546,7 +557,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
546 557
 				remove = true
547 558
 			}
548 559
 			capab, err := caps.NameToCapability(str)
549
-			if err != nil || (!remove && !SupportedCapabilities.Has(capab)) {
560
+			if err != nil || (!remove && !supportedCaps.Has(capab)) {
550 561
 				badCaps = true
551 562
 			} else if !remove {
552 563
 				toAdd.Enable(capab)
@@ -556,6 +567,20 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
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 584
 	switch subCommand {
560 585
 	case "LS":
561 586
 		if !client.registered {
@@ -568,14 +593,11 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
568 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 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 602
 	case "REQ":
581 603
 		if !client.registered {

+ 41
- 76
irc/server.go View File

@@ -39,13 +39,9 @@ var (
39 39
 	// supportedChannelModesString acts as a cache for when we introduce users
40 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 47
 // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
@@ -340,6 +336,11 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
340 336
 		return
341 337
 	}
342 338
 
339
+	if c.isSTSOnly {
340
+		server.playRegistrationBurst(session)
341
+		return true
342
+	}
343
+
343 344
 	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
344 345
 	// before completing the other registration commands
345 346
 	authOutcome := c.isAuthorized(server.Config())
@@ -407,6 +408,13 @@ func (server *Server) playRegistrationBurst(session *Session) {
407 408
 	//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
408 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 418
 	rb := NewResponseBuffer(session)
411 419
 	server.RplISupport(c, rb)
412 420
 	server.Lusers(c, rb)
@@ -623,23 +631,17 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
623 631
 	server.logger.Debug("server", "Regenerating HELP indexes for new languages")
624 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 635
 		updatedCaps.Add(caps.Languages)
630
-		CapValues.Set(caps.Languages, newLanguageValue)
631 636
 	}
632 637
 
633 638
 	// SASL
634 639
 	authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled
635 640
 	if config.Accounts.AuthenticationEnabled && (oldConfig == nil || !authPreviouslyEnabled) {
636 641
 		// enabling SASL
637
-		SupportedCapabilities.Enable(caps.SASL)
638
-		CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
639 642
 		addedCaps.Add(caps.SASL)
640 643
 	} else if !config.Accounts.AuthenticationEnabled && (oldConfig == nil || authPreviouslyEnabled) {
641 644
 		// disabling SASL
642
-		SupportedCapabilities.Disable(caps.SASL)
643 645
 		removedCaps.Add(caps.SASL)
644 646
 	}
645 647
 
@@ -661,39 +663,17 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
661 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 666
 	// STS
674 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 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 679
 	// resize history buffers as needed
@@ -708,42 +688,35 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
708 688
 
709 689
 	// burst new and removed caps
710 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 694
 	// updated caps get DEL'd and then NEW'd
715 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 697
 	addedCaps.Union(updatedCaps)
718 698
 	removedCaps.Union(updatedCaps)
719 699
 
720 700
 	if !addedCaps.Empty() || !removedCaps.Empty() {
721 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 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 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 710
 		// DEL caps and then send NEW ones so that updated caps get removed/added correctly
742 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 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,15 +878,11 @@ func (server *Server) setupListeners(config *Config) (err error) {
905 878
 		}
906 879
 	}
907 880
 
881
+	publicPlaintextListener := ""
908 882
 	// create new listeners that were not previously configured
909
-	numTlsListeners := 0
910
-	hasStandardTlsListener := false
911 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 887
 		_, exists := server.listeners[newAddr]
919 888
 		if !exists {
@@ -929,12 +898,8 @@ func (server *Server) setupListeners(config *Config) (err error) {
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 905
 	return

+ 10
- 6
oragono.yaml View File

@@ -15,18 +15,22 @@ server:
15 15
         # The standard plaintext port for IRC is 6667. This will listen on all interfaces:
16 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 28
         # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
19 29
         ":6697":
20 30
             tls:
21 31
                 key: tls.key
22 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 34
         # Example of a Unix domain socket for proxying:
31 35
         # "/tmp/oragono_sock":
32 36
 

Loading…
Cancel
Save