Browse Source

Merge pull request #673 from slingamn/proxytls.18

fix #561, take two
tags/v2.0.0-rc1
Shivaram Lingamneni 4 years ago
parent
commit
fec1139dc8
No account linked to committer's email address
5 changed files with 94 additions and 27 deletions
  1. 18
    13
      irc/client.go
  2. 17
    9
      irc/config.go
  3. 43
    2
      irc/gateways.go
  4. 12
    3
      irc/server.go
  5. 4
    0
      oragono.yaml

+ 18
- 13
irc/client.go View File

@@ -190,11 +190,11 @@ type ClientDetails struct {
190 190
 }
191 191
 
192 192
 // RunClient sets up a new client and runs its goroutine.
193
-func (server *Server) RunClient(conn clientConn) {
193
+func (server *Server) RunClient(conn clientConn, proxyLine string) {
194 194
 	var isBanned bool
195 195
 	var banMsg string
196 196
 	var realIP net.IP
197
-	if conn.Config.IsTor {
197
+	if conn.Config.Tor {
198 198
 		realIP = utils.IPv4LoopbackAddress
199 199
 		isBanned, banMsg = server.checkTorLimits()
200 200
 	} else {
@@ -221,8 +221,8 @@ func (server *Server) RunClient(conn clientConn) {
221 221
 		atime:     now,
222 222
 		channels:  make(ChannelSet),
223 223
 		ctime:     now,
224
-		isSTSOnly: conn.Config.IsSTSOnly,
225
-		isTor:     conn.Config.IsTor,
224
+		isSTSOnly: conn.Config.STSOnly,
225
+		isTor:     conn.Config.Tor,
226 226
 		languages: server.Languages().Default(),
227 227
 		loginThrottle: connection_limits.GenericThrottle{
228 228
 			Duration: config.Accounts.LoginThrottling.Duration,
@@ -254,7 +254,7 @@ func (server *Server) RunClient(conn clientConn) {
254 254
 		client.certfp, _ = socket.CertFP()
255 255
 	}
256 256
 
257
-	if conn.Config.IsTor {
257
+	if conn.Config.Tor {
258 258
 		client.SetMode(modes.TLS, true)
259 259
 		// cover up details of the tor proxying infrastructure (not a user privacy concern,
260 260
 		// but a hardening measure):
@@ -278,7 +278,7 @@ func (server *Server) RunClient(conn clientConn) {
278 278
 	client.proxiedIP = session.proxiedIP
279 279
 
280 280
 	server.stats.Add()
281
-	client.run(session)
281
+	client.run(session, proxyLine)
282 282
 }
283 283
 
284 284
 func (client *Client) doIdentLookup(conn net.Conn) {
@@ -371,11 +371,9 @@ func (client *Client) t(originalString string) string {
371 371
 	return languageManager.Translate(client.Languages(), originalString)
372 372
 }
373 373
 
374
-//
375
-// command goroutine
376
-//
377
-
378
-func (client *Client) run(session *Session) {
374
+// main client goroutine: read lines and execute the corresponding commands
375
+// `proxyLine` is the PROXY-before-TLS line, if there was one
376
+func (client *Client) run(session *Session, proxyLine string) {
379 377
 
380 378
 	defer func() {
381 379
 		if r := recover(); r != nil {
@@ -414,7 +412,14 @@ func (client *Client) run(session *Session) {
414 412
 	for {
415 413
 		maxlenRest := session.MaxlenRest()
416 414
 
417
-		line, err := session.socket.Read()
415
+		var line string
416
+		var err error
417
+		if proxyLine == "" {
418
+			line, err = session.socket.Read()
419
+		} else {
420
+			line = proxyLine // pretend we're just now receiving the proxy-before-TLS line
421
+			proxyLine = ""
422
+		}
418 423
 		if err != nil {
419 424
 			quitMessage := "connection closed"
420 425
 			if err == errReadQ {
@@ -483,7 +488,7 @@ func (client *Client) run(session *Session) {
483 488
 			break
484 489
 		} else if session.client != client {
485 490
 			// bouncer reattach
486
-			go session.client.run(session)
491
+			go session.client.run(session, "")
487 492
 			break
488 493
 		}
489 494
 	}

+ 17
- 9
irc/config.go View File

@@ -39,8 +39,9 @@ import (
39 39
 
40 40
 // TLSListenConfig defines configuration options for listening on TLS.
41 41
 type TLSListenConfig struct {
42
-	Cert string
43
-	Key  string
42
+	Cert  string
43
+	Key   string
44
+	Proxy bool
44 45
 }
45 46
 
46 47
 // This is the YAML-deserializable type of the value of the `Server.Listeners` map
@@ -53,9 +54,10 @@ type listenerConfigBlock struct {
53 54
 // listenerConfig is the config governing a particular listener (bound address),
54 55
 // in particular whether it has TLS or Tor (or both) enabled.
55 56
 type listenerConfig struct {
56
-	TLSConfig *tls.Config
57
-	IsTor     bool
58
-	IsSTSOnly bool
57
+	TLSConfig      *tls.Config
58
+	Tor            bool
59
+	STSOnly        bool
60
+	ProxyBeforeTLS bool
59 61
 }
60 62
 
61 63
 type AccountConfig struct {
@@ -517,9 +519,9 @@ func (conf *Config) prepareListeners() (err error) {
517 519
 	if 0 < len(conf.Server.Listeners) {
518 520
 		for addr, block := range conf.Server.Listeners {
519 521
 			var lconf listenerConfig
520
-			lconf.IsTor = block.Tor
521
-			lconf.IsSTSOnly = block.STSOnly
522
-			if lconf.IsSTSOnly && !conf.Server.STS.Enabled {
522
+			lconf.Tor = block.Tor
523
+			lconf.STSOnly = block.STSOnly
524
+			if lconf.STSOnly && !conf.Server.STS.Enabled {
523 525
 				return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr)
524 526
 			}
525 527
 			if block.TLS.Cert != "" {
@@ -528,6 +530,7 @@ func (conf *Config) prepareListeners() (err error) {
528 530
 					return err
529 531
 				}
530 532
 				lconf.TLSConfig = tlsConfig
533
+				lconf.ProxyBeforeTLS = block.TLS.Proxy
531 534
 			}
532 535
 			listeners[addr] = lconf
533 536
 		}
@@ -540,7 +543,7 @@ func (conf *Config) prepareListeners() (err error) {
540 543
 		}
541 544
 		for _, addr := range conf.Server.Listen {
542 545
 			var lconf listenerConfig
543
-			lconf.IsTor = torListeners[addr]
546
+			lconf.Tor = torListeners[addr]
544 547
 			tlsListenConf, ok := conf.Server.TLSListeners[addr]
545 548
 			if ok {
546 549
 				tlsConfig, err := loadTlsConfig(tlsListenConf)
@@ -837,5 +840,10 @@ func LoadConfig(filename string) (config *Config, err error) {
837 840
 		return nil, err
838 841
 	}
839 842
 
843
+	err = config.prepareListeners()
844
+	if err != nil {
845
+		return nil, fmt.Errorf("failed to prepare listeners: %v", err)
846
+	}
847
+
840 848
 	return config, nil
841 849
 }

+ 43
- 2
irc/gateways.go View File

@@ -10,6 +10,7 @@ import (
10 10
 	"fmt"
11 11
 	"net"
12 12
 	"strings"
13
+	"time"
13 14
 
14 15
 	"github.com/oragono/oragono/irc/modes"
15 16
 	"github.com/oragono/oragono/irc/utils"
@@ -20,6 +21,13 @@ var (
20 21
 	errBadProxyLine      = errors.New("Invalid PROXY/WEBIRC command")
21 22
 )
22 23
 
24
+const (
25
+	// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
26
+	// "a 108-byte buffer is always enough to store all the line and a trailing zero
27
+	// for string processing."
28
+	maxProxyLineLen = 107
29
+)
30
+
23 31
 type webircConfig struct {
24 32
 	PasswordString string `yaml:"password"`
25 33
 	Password       []byte `yaml:"password-bytes"`
@@ -75,10 +83,10 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
75 83
 
76 84
 	client.stateMutex.Lock()
77 85
 	defer client.stateMutex.Unlock()
78
-	session.proxiedIP = parsedProxiedIP
79 86
 	client.proxiedIP = parsedProxiedIP
80
-	session.rawHostname = rawHostname
81 87
 	client.rawHostname = rawHostname
88
+	session.proxiedIP = parsedProxiedIP
89
+	session.rawHostname = rawHostname
82 90
 	client.cloakedHostname = cloakedHostname
83 91
 	// nickmask will be updated when the client completes registration
84 92
 	// set tls info
@@ -118,3 +126,36 @@ func handleProxyCommand(server *Server, client *Client, session *Session, line s
118 126
 		return errBadGatewayAddress
119 127
 	}
120 128
 }
129
+
130
+// read a PROXY line one byte at a time, to ensure we don't read anything beyond
131
+// that into a buffer, which would break the TLS handshake
132
+func readRawProxyLine(conn net.Conn) (result string) {
133
+	// normally this is covered by ping timeouts, but we're doing this outside
134
+	// of the normal client goroutine:
135
+	conn.SetDeadline(time.Now().Add(time.Minute))
136
+	defer conn.SetDeadline(time.Time{})
137
+
138
+	var buf [maxProxyLineLen]byte
139
+	oneByte := make([]byte, 1)
140
+	i := 0
141
+	for i < maxProxyLineLen {
142
+		n, err := conn.Read(oneByte)
143
+		if err != nil {
144
+			return
145
+		} else if n == 1 {
146
+			buf[i] = oneByte[0]
147
+			if buf[i] == '\n' {
148
+				candidate := string(buf[0 : i+1])
149
+				if strings.HasPrefix(candidate, "PROXY") {
150
+					return candidate
151
+				} else {
152
+					return
153
+				}
154
+			}
155
+			i += 1
156
+		}
157
+	}
158
+
159
+	// no \r\n, fail out
160
+	return
161
+}

+ 12
- 3
irc/server.go View File

@@ -304,6 +304,15 @@ func (server *Server) createListener(addr string, conf listenerConfig, bindMode
304 304
 				listener.Close()
305 305
 				return
306 306
 			} else if err == nil {
307
+				var proxyLine string
308
+				if conf.ProxyBeforeTLS {
309
+					proxyLine = readRawProxyLine(conn)
310
+					if proxyLine == "" {
311
+						server.logger.Error("internal", "bad TLS-proxy line from", addr)
312
+						conn.Close()
313
+						continue
314
+					}
315
+				}
307 316
 				if conf.TLSConfig != nil {
308 317
 					conn = tls.Server(conn, conf.TLSConfig)
309 318
 				}
@@ -312,7 +321,7 @@ func (server *Server) createListener(addr string, conf listenerConfig, bindMode
312 321
 					Config: conf,
313 322
 				}
314 323
 				// hand off the connection
315
-				go server.RunClient(newConn)
324
+				go server.RunClient(newConn, proxyLine)
316 325
 			} else {
317 326
 				server.logger.Error("internal", "accept error", addr, err.Error())
318 327
 			}
@@ -857,7 +866,7 @@ func (server *Server) loadDatastore(config *Config) error {
857 866
 func (server *Server) setupListeners(config *Config) (err error) {
858 867
 	logListener := func(addr string, config listenerConfig) {
859 868
 		server.logger.Info("listeners",
860
-			fmt.Sprintf("now listening on %s, tls=%t, tor=%t.", addr, (config.TLSConfig != nil), config.IsTor),
869
+			fmt.Sprintf("now listening on %s, tls=%t, tlsproxy=%t, tor=%t.", addr, (config.TLSConfig != nil), config.ProxyBeforeTLS, config.Tor),
861 870
 		)
862 871
 	}
863 872
 
@@ -884,7 +893,7 @@ func (server *Server) setupListeners(config *Config) (err error) {
884 893
 	publicPlaintextListener := ""
885 894
 	// create new listeners that were not previously configured
886 895
 	for newAddr, newConfig := range config.Server.trueListeners {
887
-		if strings.HasPrefix(newAddr, ":") && !newConfig.IsTor && !newConfig.IsSTSOnly && newConfig.TLSConfig == nil {
896
+		if strings.HasPrefix(newAddr, ":") && !newConfig.Tor && !newConfig.STSOnly && newConfig.TLSConfig == nil {
888 897
 			publicPlaintextListener = newAddr
889 898
 		}
890 899
 		_, exists := server.listeners[newAddr]

+ 4
- 0
oragono.yaml View File

@@ -30,6 +30,10 @@ server:
30 30
             tls:
31 31
                 key: tls.key
32 32
                 cert: tls.crt
33
+                # 'proxy' should typically be false. It's only for Kubernetes-style load
34
+                # balancing that does not terminate TLS, but sends an initial PROXY line
35
+                # in plaintext.
36
+                proxy: false
33 37
 
34 38
         # Example of a Unix domain socket for proxying:
35 39
         # "/tmp/oragono_sock":

Loading…
Cancel
Save