Просмотр исходного кода

more work on websocket support

tags/v2.1.0-rc1
Shivaram Lingamneni 4 лет назад
Родитель
Сommit
3dc5c8de78
17 измененных файлов: 830 добавлений и 444 удалений
  1. 15
    3
      conventional.yaml
  2. 37
    44
      irc/client.go
  3. 23
    19
      irc/config.go
  4. 12
    48
      irc/gateways.go
  5. 2
    1
      irc/handlers.go
  6. 136
    0
      irc/ircconn.go
  7. 209
    0
      irc/listeners.go
  8. 23
    204
      irc/server.go
  9. 5
    45
      irc/socket.go
  10. 34
    0
      irc/utils/crypto.go
  11. 30
    0
      irc/utils/glob.go
  12. 37
    0
      irc/utils/glob_test.go
  13. 42
    7
      irc/utils/net.go
  14. 36
    0
      irc/utils/net_test.go
  15. 174
    0
      irc/utils/proxy.go
  16. 0
    70
      irc/websocket.go
  17. 15
    3
      oragono.yaml

+ 15
- 3
conventional.yaml Просмотреть файл

40
         # "/hidden_service_sockets/oragono_tor_sock":
40
         # "/hidden_service_sockets/oragono_tor_sock":
41
         #     tor: true
41
         #     tor: true
42
 
42
 
43
-        # Example of a WebSocket listener.
44
-        #"127.0.0.1:8080":
45
-        #    websocket: true
43
+        # Example of a WebSocket listener:
44
+        # ":4430":
45
+        #     websocket: true
46
+        #     tls:
47
+        #         key: tls.key
48
+        #         cert: tls.crt
46
 
49
 
47
     # sets the permissions for Unix listen sockets. on a typical Linux system,
50
     # sets the permissions for Unix listen sockets. on a typical Linux system,
48
     # the default is 0775 or 0755, which prevents other users/groups from connecting
51
     # the default is 0775 or 0755, which prevents other users/groups from connecting
85
         # should clients include this STS policy when they ship their inbuilt preload lists?
88
         # should clients include this STS policy when they ship their inbuilt preload lists?
86
         preload: false
89
         preload: false
87
 
90
 
91
+    websockets:
92
+        # sets the Origin headers that will be accepted for websocket connections.
93
+        # an empty list means any value (or no value) is allowed. the main use of this
94
+        # is to prevent malicious third-party Javascript from co-opting non-malicious
95
+        # clients (i.e., mainstream browsers) to DDoS your server.
96
+        allowed-origins:
97
+            # - "https://oragono.io"
98
+            # - "https://*.oragono.io"
99
+
88
     # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
100
     # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
89
     # channel names, account names, etc.), and how they are normalized for case.
101
     # channel names, account names, etc.), and how they are normalized for case.
90
     # with the recommended default of 'precis', utf-8 identifiers that are "sane"
102
     # with the recommended default of 'precis', utf-8 identifiers that are "sane"

+ 37
- 44
irc/client.go Просмотреть файл

254
 }
254
 }
255
 
255
 
256
 // RunClient sets up a new client and runs its goroutine.
256
 // RunClient sets up a new client and runs its goroutine.
257
-func (server *Server) RunClient(conn clientConn, proxyLine string) {
257
+func (server *Server) RunClient(conn IRCConn) {
258
+	proxiedConn := conn.UnderlyingConn()
258
 	var isBanned bool
259
 	var isBanned bool
259
 	var banMsg string
260
 	var banMsg string
260
-	var realIP net.IP
261
-	if conn.Config.Tor {
262
-		realIP = utils.IPv4LoopbackAddress
261
+	realIP := utils.AddrToIP(proxiedConn.RemoteAddr())
262
+	var proxiedIP net.IP
263
+	if proxiedConn.Config.Tor {
264
+		// cover up details of the tor proxying infrastructure (not a user privacy concern,
265
+		// but a hardening measure):
266
+		proxiedIP = utils.IPv4LoopbackAddress
263
 		isBanned, banMsg = server.checkTorLimits()
267
 		isBanned, banMsg = server.checkTorLimits()
264
 	} else {
268
 	} else {
265
-		realIP = utils.AddrToIP(conn.Conn.RemoteAddr())
266
-		// skip the ban check for k8s-style proxy-before-TLS
267
-		if proxyLine == "" {
268
-			isBanned, banMsg = server.checkBans(realIP)
269
+		ipToCheck := realIP
270
+		if proxiedConn.ProxiedIP != nil {
271
+			proxiedIP = proxiedConn.ProxiedIP
272
+			ipToCheck = proxiedIP
269
 		}
273
 		}
274
+		isBanned, banMsg = server.checkBans(ipToCheck)
270
 	}
275
 	}
271
 
276
 
272
 	if isBanned {
277
 	if isBanned {
273
 		// this might not show up properly on some clients,
278
 		// this might not show up properly on some clients,
274
 		// but our objective here is just to close the connection out before it has a load impact on us
279
 		// but our objective here is just to close the connection out before it has a load impact on us
275
-		conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
276
-		conn.Conn.Close()
280
+		conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
281
+		conn.Close()
277
 		return
282
 		return
278
 	}
283
 	}
279
 
284
 
282
 	now := time.Now().UTC()
287
 	now := time.Now().UTC()
283
 	config := server.Config()
288
 	config := server.Config()
284
 	// give them 1k of grace over the limit:
289
 	// give them 1k of grace over the limit:
285
-	socket := NewSocket(conn.Conn, ircmsg.MaxlenTagsFromClient+512+1024, config.Server.MaxSendQBytes)
290
+	socket := NewSocket(conn, config.Server.MaxSendQBytes)
286
 	client := &Client{
291
 	client := &Client{
287
 		lastSeen:   now,
292
 		lastSeen:   now,
288
 		lastActive: now,
293
 		lastActive: now,
289
 		channels:   make(ChannelSet),
294
 		channels:   make(ChannelSet),
290
 		ctime:      now,
295
 		ctime:      now,
291
-		isSTSOnly:  conn.Config.STSOnly,
296
+		isSTSOnly:  proxiedConn.Config.STSOnly,
292
 		languages:  server.Languages().Default(),
297
 		languages:  server.Languages().Default(),
293
 		loginThrottle: connection_limits.GenericThrottle{
298
 		loginThrottle: connection_limits.GenericThrottle{
294
 			Duration: config.Accounts.LoginThrottling.Duration,
299
 			Duration: config.Accounts.LoginThrottling.Duration,
299
 		nick:           "*", // * is used until actual nick is given
304
 		nick:           "*", // * is used until actual nick is given
300
 		nickCasefolded: "*",
305
 		nickCasefolded: "*",
301
 		nickMaskString: "*", // * is used until actual nick is given
306
 		nickMaskString: "*", // * is used until actual nick is given
307
+		realIP:         realIP,
308
+		proxiedIP:      proxiedIP,
302
 	}
309
 	}
303
 	client.writerSemaphore.Initialize(1)
310
 	client.writerSemaphore.Initialize(1)
304
 	client.history.Initialize(config.History.ClientLength, config.History.AutoresizeWindow)
311
 	client.history.Initialize(config.History.ClientLength, config.History.AutoresizeWindow)
311
 		ctime:      now,
318
 		ctime:      now,
312
 		lastActive: now,
319
 		lastActive: now,
313
 		realIP:     realIP,
320
 		realIP:     realIP,
314
-		isTor:      conn.Config.Tor,
321
+		proxiedIP:  proxiedIP,
322
+		isTor:      proxiedConn.Config.Tor,
315
 	}
323
 	}
316
 	client.sessions = []*Session{session}
324
 	client.sessions = []*Session{session}
317
 
325
 
322
 		client.SetMode(defaultMode, true)
330
 		client.SetMode(defaultMode, true)
323
 	}
331
 	}
324
 
332
 
325
-	if conn.Config.TLSConfig != nil {
333
+	if proxiedConn.Config.TLSConfig != nil {
326
 		client.SetMode(modes.TLS, true)
334
 		client.SetMode(modes.TLS, true)
327
 		// error is not useful to us here anyways so we can ignore it
335
 		// error is not useful to us here anyways so we can ignore it
328
-		session.certfp, _ = socket.CertFP()
336
+		session.certfp, _ = utils.GetCertFP(proxiedConn.Conn, RegisterTimeout)
329
 	}
337
 	}
330
 
338
 
331
-	if conn.Config.Tor {
339
+	if session.isTor {
332
 		client.SetMode(modes.TLS, true)
340
 		client.SetMode(modes.TLS, true)
333
-		// cover up details of the tor proxying infrastructure (not a user privacy concern,
334
-		// but a hardening measure):
335
-		session.proxiedIP = utils.IPv4LoopbackAddress
336
-		client.proxiedIP = session.proxiedIP
337
 		session.rawHostname = config.Server.TorListeners.Vhost
341
 		session.rawHostname = config.Server.TorListeners.Vhost
338
 		client.rawHostname = session.rawHostname
342
 		client.rawHostname = session.rawHostname
339
 	} else {
343
 	} else {
340
-		remoteAddr := conn.Conn.RemoteAddr()
341
 		if realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) {
344
 		if realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) {
342
 			// treat local connections as secure (may be overridden later by WEBIRC)
345
 			// treat local connections as secure (may be overridden later by WEBIRC)
343
 			client.SetMode(modes.TLS, true)
346
 			client.SetMode(modes.TLS, true)
344
 		}
347
 		}
345
-		if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
346
-			client.doIdentLookup(conn.Conn)
348
+		if config.Server.CheckIdent {
349
+			client.doIdentLookup(proxiedConn.Conn)
347
 		}
350
 		}
348
 	}
351
 	}
349
-	client.realIP = session.realIP
350
 
352
 
351
 	server.stats.Add()
353
 	server.stats.Add()
352
-	client.run(session, proxyLine)
354
+	client.run(session)
353
 }
355
 }
354
 
356
 
355
 func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) {
357
 func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) {
476
 }
478
 }
477
 
479
 
478
 func (client *Client) doIdentLookup(conn net.Conn) {
480
 func (client *Client) doIdentLookup(conn net.Conn) {
479
-	_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
480
-	if err != nil {
481
-		client.server.logger.Error("internal", "bad server address", err.Error())
481
+	localTCPAddr, ok := conn.LocalAddr().(*net.TCPAddr)
482
+	if !ok {
482
 		return
483
 		return
483
 	}
484
 	}
484
-	serverPort, _ := strconv.Atoi(serverPortString)
485
-	clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String())
486
-	if err != nil {
487
-		client.server.logger.Error("internal", "bad client address", err.Error())
485
+	serverPort := localTCPAddr.Port
486
+	remoteTCPAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
487
+	if !ok {
488
 		return
488
 		return
489
 	}
489
 	}
490
-	clientPort, _ := strconv.Atoi(clientPortString)
490
+	clientPort := remoteTCPAddr.Port
491
 
491
 
492
 	client.Notice(client.t("*** Looking up your username"))
492
 	client.Notice(client.t("*** Looking up your username"))
493
-	resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
493
+	resp, err := ident.Query(remoteTCPAddr.IP.String(), serverPort, clientPort, IdentTimeoutSeconds)
494
 	if err == nil {
494
 	if err == nil {
495
 		err := client.SetNames(resp.Identifier, "", true)
495
 		err := client.SetNames(resp.Identifier, "", true)
496
 		if err == nil {
496
 		if err == nil {
567
 
567
 
568
 // main client goroutine: read lines and execute the corresponding commands
568
 // main client goroutine: read lines and execute the corresponding commands
569
 // `proxyLine` is the PROXY-before-TLS line, if there was one
569
 // `proxyLine` is the PROXY-before-TLS line, if there was one
570
-func (client *Client) run(session *Session, proxyLine string) {
570
+func (client *Client) run(session *Session) {
571
 
571
 
572
 	defer func() {
572
 	defer func() {
573
 		if r := recover(); r != nil {
573
 		if r := recover(); r != nil {
601
 	firstLine := !isReattach
601
 	firstLine := !isReattach
602
 
602
 
603
 	for {
603
 	for {
604
-		var line string
605
-		var err error
606
-		if proxyLine == "" {
607
-			line, err = session.socket.Read()
608
-		} else {
609
-			line = proxyLine // pretend we're just now receiving the proxy-before-TLS line
610
-			proxyLine = ""
611
-		}
604
+		line, err := session.socket.Read()
612
 		if err != nil {
605
 		if err != nil {
613
 			quitMessage := "connection closed"
606
 			quitMessage := "connection closed"
614
 			if err == errReadQ {
607
 			if err == errReadQ {
681
 			break
674
 			break
682
 		} else if session.client != client {
675
 		} else if session.client != client {
683
 			// bouncer reattach
676
 			// bouncer reattach
684
-			go session.client.run(session, "")
677
+			go session.client.run(session)
685
 			break
678
 			break
686
 		}
679
 		}
687
 	}
680
 	}

+ 23
- 19
irc/config.go Просмотреть файл

56
 	WebSocket bool
56
 	WebSocket bool
57
 }
57
 }
58
 
58
 
59
-// listenerConfig is the config governing a particular listener (bound address),
60
-// in particular whether it has TLS or Tor (or both) enabled.
61
-type listenerConfig struct {
62
-	TLSConfig      *tls.Config
63
-	Tor            bool
64
-	STSOnly        bool
65
-	ProxyBeforeTLS bool
66
-	WebSocket      bool
67
-}
68
-
69
 type PersistentStatus uint
59
 type PersistentStatus uint
70
 
60
 
71
 const (
61
 const (
488
 		Listeners    map[string]listenerConfigBlock
478
 		Listeners    map[string]listenerConfigBlock
489
 		UnixBindMode os.FileMode        `yaml:"unix-bind-mode"`
479
 		UnixBindMode os.FileMode        `yaml:"unix-bind-mode"`
490
 		TorListeners TorListenersConfig `yaml:"tor-listeners"`
480
 		TorListeners TorListenersConfig `yaml:"tor-listeners"`
481
+		Websockets   struct {
482
+			AllowedOrigins       []string `yaml:"allowed-origins"`
483
+			allowedOriginRegexps []*regexp.Regexp
484
+		}
491
 		// they get parsed into this internal representation:
485
 		// they get parsed into this internal representation:
492
-		trueListeners           map[string]listenerConfig
486
+		trueListeners           map[string]utils.ListenerConfig
493
 		STS                     STSConfig
487
 		STS                     STSConfig
494
 		LookupHostnames         *bool `yaml:"lookup-hostnames"`
488
 		LookupHostnames         *bool `yaml:"lookup-hostnames"`
495
 		lookupHostnames         bool
489
 		lookupHostnames         bool
767
 		return fmt.Errorf("No listeners were configured")
761
 		return fmt.Errorf("No listeners were configured")
768
 	}
762
 	}
769
 
763
 
770
-	conf.Server.trueListeners = make(map[string]listenerConfig)
764
+	conf.Server.trueListeners = make(map[string]utils.ListenerConfig)
771
 	for addr, block := range conf.Server.Listeners {
765
 	for addr, block := range conf.Server.Listeners {
772
-		var lconf listenerConfig
766
+		var lconf utils.ListenerConfig
767
+		lconf.ProxyDeadline = time.Minute
773
 		lconf.Tor = block.Tor
768
 		lconf.Tor = block.Tor
774
 		lconf.STSOnly = block.STSOnly
769
 		lconf.STSOnly = block.STSOnly
775
 		if lconf.STSOnly && !conf.Server.STS.Enabled {
770
 		if lconf.STSOnly && !conf.Server.STS.Enabled {
781
 				return err
776
 				return err
782
 			}
777
 			}
783
 			lconf.TLSConfig = tlsConfig
778
 			lconf.TLSConfig = tlsConfig
784
-			lconf.ProxyBeforeTLS = block.TLS.Proxy
779
+			lconf.RequireProxy = block.TLS.Proxy
785
 		}
780
 		}
786
 		lconf.WebSocket = block.WebSocket
781
 		lconf.WebSocket = block.WebSocket
787
 		conf.Server.trueListeners[addr] = lconf
782
 		conf.Server.trueListeners[addr] = lconf
849
 		return nil, fmt.Errorf("failed to prepare listeners: %v", err)
844
 		return nil, fmt.Errorf("failed to prepare listeners: %v", err)
850
 	}
845
 	}
851
 
846
 
847
+	for _, glob := range config.Server.Websockets.AllowedOrigins {
848
+		globre, err := utils.CompileGlob(glob)
849
+		if err != nil {
850
+			return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob)
851
+		}
852
+		config.Server.Websockets.allowedOriginRegexps = append(config.Server.Websockets.allowedOriginRegexps, globre)
853
+	}
854
+
852
 	if config.Server.STS.Enabled {
855
 	if config.Server.STS.Enabled {
853
 		if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
856
 		if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
854
 			return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
857
 			return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
1206
 }
1209
 }
1207
 
1210
 
1208
 func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) {
1211
 func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) {
1212
+	standard, err = utils.CompileGlob(guestFormat)
1213
+	if err != nil {
1214
+		return
1215
+	}
1216
+
1209
 	starIndex := strings.IndexByte(guestFormat, '*')
1217
 	starIndex := strings.IndexByte(guestFormat, '*')
1210
 	if starIndex == -1 {
1218
 	if starIndex == -1 {
1211
 		return nil, nil, errors.New("guest format must contain exactly one *")
1219
 		return nil, nil, errors.New("guest format must contain exactly one *")
1215
 	if strings.IndexByte(final, '*') != -1 {
1223
 	if strings.IndexByte(final, '*') != -1 {
1216
 		return nil, nil, errors.New("guest format must contain exactly one *")
1224
 		return nil, nil, errors.New("guest format must contain exactly one *")
1217
 	}
1225
 	}
1218
-	standard, err = regexp.Compile(fmt.Sprintf("^%s(.*)%s$", initial, final))
1219
-	if err != nil {
1220
-		return
1221
-	}
1222
 	initialFolded, err := casefoldWithSetting(initial, casemapping)
1226
 	initialFolded, err := casefoldWithSetting(initial, casemapping)
1223
 	if err != nil {
1227
 	if err != nil {
1224
 		return
1228
 		return
1227
 	if err != nil {
1231
 	if err != nil {
1228
 		return
1232
 		return
1229
 	}
1233
 	}
1230
-	folded, err = regexp.Compile(fmt.Sprintf("^%s(.*)%s$", initialFolded, finalFolded))
1234
+	folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded))
1231
 	return
1235
 	return
1232
 }
1236
 }

+ 12
- 48
irc/gateways.go Просмотреть файл

7
 
7
 
8
 import (
8
 import (
9
 	"errors"
9
 	"errors"
10
-	"fmt"
11
 	"net"
10
 	"net"
12
-	"strings"
13
-	"time"
14
 
11
 
15
 	"github.com/oragono/oragono/irc/modes"
12
 	"github.com/oragono/oragono/irc/modes"
16
 	"github.com/oragono/oragono/irc/utils"
13
 	"github.com/oragono/oragono/irc/utils"
58
 }
55
 }
59
 
56
 
60
 // ApplyProxiedIP applies the given IP to the client.
57
 // ApplyProxiedIP applies the given IP to the client.
61
-func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls bool) (err error, quitMsg string) {
58
+func (client *Client) ApplyProxiedIP(session *Session, proxiedIP net.IP, tls bool) (err error, quitMsg string) {
62
 	// PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself
59
 	// PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself
63
 	// is whitelisted:
60
 	// is whitelisted:
64
 	if session.isTor {
61
 	if session.isTor {
66
 	}
63
 	}
67
 
64
 
68
 	// ensure IP is sane
65
 	// ensure IP is sane
69
-	parsedProxiedIP := net.ParseIP(proxiedIP).To16()
70
-	if parsedProxiedIP == nil {
71
-		return errBadProxyLine, fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP)
66
+	if proxiedIP == nil {
67
+		return errBadProxyLine, "proxied IP is not valid"
72
 	}
68
 	}
69
+	proxiedIP = proxiedIP.To16()
73
 
70
 
74
-	isBanned, banMsg := client.server.checkBans(parsedProxiedIP)
71
+	isBanned, banMsg := client.server.checkBans(proxiedIP)
75
 	if isBanned {
72
 	if isBanned {
76
 		return errBanned, banMsg
73
 		return errBanned, banMsg
77
 	}
74
 	}
80
 	client.server.connectionLimiter.RemoveClient(session.realIP)
77
 	client.server.connectionLimiter.RemoveClient(session.realIP)
81
 
78
 
82
 	// given IP is sane! override the client's current IP
79
 	// given IP is sane! override the client's current IP
83
-	client.server.logger.Info("connect-ip", "Accepted proxy IP for client", parsedProxiedIP.String())
80
+	client.server.logger.Info("connect-ip", "Accepted proxy IP for client", proxiedIP.String())
84
 
81
 
85
 	client.stateMutex.Lock()
82
 	client.stateMutex.Lock()
86
 	defer client.stateMutex.Unlock()
83
 	defer client.stateMutex.Unlock()
87
-	client.proxiedIP = parsedProxiedIP
88
-	session.proxiedIP = parsedProxiedIP
84
+	client.proxiedIP = proxiedIP
85
+	session.proxiedIP = proxiedIP
89
 	// nickmask will be updated when the client completes registration
86
 	// nickmask will be updated when the client completes registration
90
 	// set tls info
87
 	// set tls info
91
 	session.certfp = ""
88
 	session.certfp = ""
110
 		}
107
 		}
111
 	}()
108
 	}()
112
 
109
 
113
-	params := strings.Fields(line)
114
-	if len(params) != 6 {
115
-		return errBadProxyLine
110
+	ip, err := utils.ParseProxyLine(line)
111
+	if err != nil {
112
+		return err
116
 	}
113
 	}
117
 
114
 
118
 	if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
115
 	if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
119
 		// assume PROXY connections are always secure
116
 		// assume PROXY connections are always secure
120
-		err, quitMsg = client.ApplyProxiedIP(session, params[2], true)
117
+		err, quitMsg = client.ApplyProxiedIP(session, ip, true)
121
 		return
118
 		return
122
 	} else {
119
 	} else {
123
 		// real source IP is not authorized to issue PROXY:
120
 		// real source IP is not authorized to issue PROXY:
124
 		return errBadGatewayAddress
121
 		return errBadGatewayAddress
125
 	}
122
 	}
126
 }
123
 }
127
-
128
-// read a PROXY line one byte at a time, to ensure we don't read anything beyond
129
-// that into a buffer, which would break the TLS handshake
130
-func readRawProxyLine(conn net.Conn) (result string) {
131
-	// normally this is covered by ping timeouts, but we're doing this outside
132
-	// of the normal client goroutine:
133
-	conn.SetDeadline(time.Now().Add(time.Minute))
134
-	defer conn.SetDeadline(time.Time{})
135
-
136
-	var buf [maxProxyLineLen]byte
137
-	oneByte := make([]byte, 1)
138
-	i := 0
139
-	for i < maxProxyLineLen {
140
-		n, err := conn.Read(oneByte)
141
-		if err != nil {
142
-			return
143
-		} else if n == 1 {
144
-			buf[i] = oneByte[0]
145
-			if buf[i] == '\n' {
146
-				candidate := string(buf[0 : i+1])
147
-				if strings.HasPrefix(candidate, "PROXY") {
148
-					return candidate
149
-				} else {
150
-					return
151
-				}
152
-			}
153
-			i += 1
154
-		}
155
-	}
156
-
157
-	// no \r\n, fail out
158
-	return
159
-}

+ 2
- 1
irc/handlers.go Просмотреть файл

10
 	"bytes"
10
 	"bytes"
11
 	"encoding/base64"
11
 	"encoding/base64"
12
 	"fmt"
12
 	"fmt"
13
+	"net"
13
 	"os"
14
 	"os"
14
 	"runtime"
15
 	"runtime"
15
 	"runtime/debug"
16
 	"runtime/debug"
2581
 				continue
2582
 				continue
2582
 			}
2583
 			}
2583
 
2584
 
2584
-			err, quitMsg := client.ApplyProxiedIP(rb.session, msg.Params[3], secure)
2585
+			err, quitMsg := client.ApplyProxiedIP(rb.session, net.ParseIP(msg.Params[3]), secure)
2585
 			if err != nil {
2586
 			if err != nil {
2586
 				client.Quit(quitMsg, rb.session)
2587
 				client.Quit(quitMsg, rb.session)
2587
 				return true
2588
 				return true

+ 136
- 0
irc/ircconn.go Просмотреть файл

1
+package irc
2
+
3
+import (
4
+	"bufio"
5
+	"bytes"
6
+	"net"
7
+	"unicode/utf8"
8
+
9
+	"github.com/gorilla/websocket"
10
+	"github.com/goshuirc/irc-go/ircmsg"
11
+
12
+	"github.com/oragono/oragono/irc/utils"
13
+)
14
+
15
+const (
16
+	maxReadQBytes = ircmsg.MaxlenTagsFromClient + 512 + 1024
17
+)
18
+
19
+var (
20
+	crlf = []byte{'\r', '\n'}
21
+)
22
+
23
+// IRCConn abstracts away the distinction between a regular
24
+// net.Conn (which includes both raw TCP and TLS) and a websocket.
25
+// it doesn't expose Read and Write because websockets are message-oriented,
26
+// not stream-oriented.
27
+type IRCConn interface {
28
+	UnderlyingConn() *utils.ProxiedConnection
29
+
30
+	Write([]byte) error
31
+	WriteBuffers([][]byte) error
32
+	ReadLine() (line []byte, err error)
33
+
34
+	Close() error
35
+}
36
+
37
+// IRCStreamConn is an IRCConn over a regular stream connection.
38
+type IRCStreamConn struct {
39
+	conn   *utils.ProxiedConnection
40
+	reader *bufio.Reader
41
+}
42
+
43
+func NewIRCStreamConn(conn *utils.ProxiedConnection) *IRCStreamConn {
44
+	return &IRCStreamConn{
45
+		conn: conn,
46
+	}
47
+}
48
+
49
+func (cc *IRCStreamConn) UnderlyingConn() *utils.ProxiedConnection {
50
+	return cc.conn
51
+}
52
+
53
+func (cc *IRCStreamConn) Write(buf []byte) (err error) {
54
+	_, err = cc.conn.Write(buf)
55
+	return
56
+}
57
+
58
+func (cc *IRCStreamConn) WriteBuffers(buffers [][]byte) (err error) {
59
+	// on Linux, with a plaintext TCP or Unix domain socket,
60
+	// the Go runtime will optimize this into a single writev(2) call:
61
+	_, err = (*net.Buffers)(&buffers).WriteTo(cc.conn)
62
+	return
63
+}
64
+
65
+func (cc *IRCStreamConn) ReadLine() (line []byte, err error) {
66
+	// lazy initialize the reader in case the IP is banned
67
+	if cc.reader == nil {
68
+		cc.reader = bufio.NewReaderSize(cc.conn, maxReadQBytes)
69
+	}
70
+
71
+	var isPrefix bool
72
+	line, isPrefix, err = cc.reader.ReadLine()
73
+	if isPrefix {
74
+		return nil, errReadQ
75
+	}
76
+	line = bytes.TrimSuffix(line, crlf)
77
+	return
78
+}
79
+
80
+func (cc *IRCStreamConn) Close() (err error) {
81
+	return cc.conn.Close()
82
+}
83
+
84
+// IRCWSConn is an IRCConn over a websocket.
85
+type IRCWSConn struct {
86
+	conn *websocket.Conn
87
+}
88
+
89
+func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
90
+	return IRCWSConn{conn: conn}
91
+}
92
+
93
+func (wc IRCWSConn) UnderlyingConn() *utils.ProxiedConnection {
94
+	pConn, ok := wc.conn.UnderlyingConn().(*utils.ProxiedConnection)
95
+	if ok {
96
+		return pConn
97
+	} else {
98
+		// this can't happen
99
+		return nil
100
+	}
101
+}
102
+
103
+func (wc IRCWSConn) Write(buf []byte) (err error) {
104
+	buf = bytes.TrimSuffix(buf, crlf)
105
+	// there's not much we can do about this;
106
+	// silently drop the message
107
+	if !utf8.Valid(buf) {
108
+		return nil
109
+	}
110
+	return wc.conn.WriteMessage(websocket.TextMessage, buf)
111
+}
112
+
113
+func (wc IRCWSConn) WriteBuffers(buffers [][]byte) (err error) {
114
+	for _, buf := range buffers {
115
+		err = wc.Write(buf)
116
+		if err != nil {
117
+			return
118
+		}
119
+	}
120
+	return
121
+}
122
+
123
+func (wc IRCWSConn) ReadLine() (line []byte, err error) {
124
+	for {
125
+		var messageType int
126
+		messageType, line, err = wc.conn.ReadMessage()
127
+		// on empty message or non-text message, try again, block if necessary
128
+		if err != nil || (messageType == websocket.TextMessage && len(line) != 0) {
129
+			return
130
+		}
131
+	}
132
+}
133
+
134
+func (wc IRCWSConn) Close() (err error) {
135
+	return wc.conn.Close()
136
+}

+ 209
- 0
irc/listeners.go Просмотреть файл

1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"errors"
8
+	"net"
9
+	"net/http"
10
+	"os"
11
+	"strings"
12
+	"sync"
13
+	"time"
14
+
15
+	"github.com/gorilla/websocket"
16
+
17
+	"github.com/oragono/oragono/irc/utils"
18
+)
19
+
20
+var (
21
+	errCantReloadListener = errors.New("can't switch a listener between stream and websocket")
22
+)
23
+
24
+// IRCListener is an abstract wrapper for a listener (TCP port or unix domain socket).
25
+// Server tracks these by listen address and can reload or stop them during rehash.
26
+type IRCListener interface {
27
+	Reload(config utils.ListenerConfig) error
28
+	Stop() error
29
+}
30
+
31
+// NewListener creates a new listener according to the specifications in the config file
32
+func NewListener(server *Server, addr string, config utils.ListenerConfig, bindMode os.FileMode) (result IRCListener, err error) {
33
+	baseListener, err := createBaseListener(addr, bindMode)
34
+	if err != nil {
35
+		return
36
+	}
37
+
38
+	wrappedListener := utils.NewReloadableListener(baseListener, config)
39
+
40
+	if config.WebSocket {
41
+		return NewWSListener(server, addr, wrappedListener, config)
42
+	} else {
43
+		return NewNetListener(server, addr, wrappedListener, config)
44
+	}
45
+}
46
+
47
+func createBaseListener(addr string, bindMode os.FileMode) (listener net.Listener, err error) {
48
+	addr = strings.TrimPrefix(addr, "unix:")
49
+	if strings.HasPrefix(addr, "/") {
50
+		// https://stackoverflow.com/a/34881585
51
+		os.Remove(addr)
52
+		listener, err = net.Listen("unix", addr)
53
+		if err == nil && bindMode != 0 {
54
+			os.Chmod(addr, bindMode)
55
+		}
56
+	} else {
57
+		listener, err = net.Listen("tcp", addr)
58
+	}
59
+	return
60
+}
61
+
62
+// NetListener is an IRCListener for a regular stream socket (TCP or unix domain)
63
+type NetListener struct {
64
+	listener *utils.ReloadableListener
65
+	server   *Server
66
+	addr     string
67
+}
68
+
69
+func NewNetListener(server *Server, addr string, listener *utils.ReloadableListener, config utils.ListenerConfig) (result *NetListener, err error) {
70
+	nl := NetListener{
71
+		server:   server,
72
+		listener: listener,
73
+		addr:     addr,
74
+	}
75
+	go nl.serve()
76
+	return &nl, nil
77
+}
78
+
79
+func (nl *NetListener) Reload(config utils.ListenerConfig) error {
80
+	if config.WebSocket {
81
+		return errCantReloadListener
82
+	}
83
+	nl.listener.Reload(config)
84
+	return nil
85
+}
86
+
87
+func (nl *NetListener) Stop() error {
88
+	return nl.listener.Close()
89
+}
90
+
91
+// ensure that any IP we got from the PROXY line is trustworthy (otherwise, clear it)
92
+func validateProxiedIP(conn *utils.ProxiedConnection, config *Config) {
93
+	if !utils.IPInNets(utils.AddrToIP(conn.RemoteAddr()), config.Server.proxyAllowedFromNets) {
94
+		conn.ProxiedIP = nil
95
+	}
96
+}
97
+
98
+func (nl *NetListener) serve() {
99
+	for {
100
+		conn, err := nl.listener.Accept()
101
+
102
+		if err == nil {
103
+			// hand off the connection
104
+			pConn, ok := conn.(*utils.ProxiedConnection)
105
+			if ok {
106
+				if pConn.ProxiedIP != nil {
107
+					validateProxiedIP(pConn, nl.server.Config())
108
+				}
109
+				go nl.server.RunClient(NewIRCStreamConn(pConn))
110
+			} else {
111
+				nl.server.logger.Error("internal", "invalid connection type", nl.addr)
112
+			}
113
+		} else if err == utils.ErrNetClosing {
114
+			return
115
+		} else {
116
+			nl.server.logger.Error("internal", "accept error", nl.addr, err.Error())
117
+		}
118
+	}
119
+}
120
+
121
+// WSListener is a listener for IRC-over-websockets (initially HTTP, then upgraded to a
122
+// different application protocol that provides a message-based API, possibly with TLS)
123
+type WSListener struct {
124
+	sync.Mutex // tier 1
125
+	listener   *utils.ReloadableListener
126
+	httpServer *http.Server
127
+	server     *Server
128
+	addr       string
129
+	config     utils.ListenerConfig
130
+}
131
+
132
+func NewWSListener(server *Server, addr string, listener *utils.ReloadableListener, config utils.ListenerConfig) (result *WSListener, err error) {
133
+	result = &WSListener{
134
+		listener: listener,
135
+		server:   server,
136
+		addr:     addr,
137
+		config:   config,
138
+	}
139
+	result.httpServer = &http.Server{
140
+		Handler:      http.HandlerFunc(result.handle),
141
+		ReadTimeout:  10 * time.Second,
142
+		WriteTimeout: 10 * time.Second,
143
+	}
144
+	go result.httpServer.Serve(listener)
145
+	return
146
+}
147
+
148
+func (wl *WSListener) Reload(config utils.ListenerConfig) error {
149
+	if !config.WebSocket {
150
+		return errCantReloadListener
151
+	}
152
+	wl.listener.Reload(config)
153
+	return nil
154
+}
155
+
156
+func (wl *WSListener) Stop() error {
157
+	return wl.httpServer.Close()
158
+}
159
+
160
+func (wl *WSListener) handle(w http.ResponseWriter, r *http.Request) {
161
+	config := wl.server.Config()
162
+	proxyAllowedFrom := config.Server.proxyAllowedFromNets
163
+	proxiedIP := utils.HandleXForwardedFor(r.RemoteAddr, r.Header.Get("X-Forwarded-For"), proxyAllowedFrom)
164
+
165
+	wsUpgrader := websocket.Upgrader{
166
+		CheckOrigin: func(r *http.Request) bool {
167
+			if len(config.Server.Websockets.allowedOriginRegexps) == 0 {
168
+				return true
169
+			}
170
+			origin := strings.TrimSpace(r.Header.Get("Origin"))
171
+			if len(origin) == 0 {
172
+				return false
173
+			}
174
+			for _, re := range config.Server.Websockets.allowedOriginRegexps {
175
+				if re.MatchString(origin) {
176
+					return true
177
+				}
178
+			}
179
+			return false
180
+		},
181
+	}
182
+
183
+	conn, err := wsUpgrader.Upgrade(w, r, nil)
184
+	if err != nil {
185
+		wl.server.logger.Info("internal", "websocket upgrade error", wl.addr, err.Error())
186
+		return
187
+	}
188
+
189
+	pConn, ok := conn.UnderlyingConn().(*utils.ProxiedConnection)
190
+	if !ok {
191
+		wl.server.logger.Error("internal", "non-proxied connection on websocket", wl.addr)
192
+		conn.Close()
193
+		return
194
+	}
195
+	if pConn.ProxiedIP != nil {
196
+		validateProxiedIP(pConn, config)
197
+	} else {
198
+		// if there was no PROXY protocol IP, use the validated X-Forwarded-For IP instead,
199
+		// unless it is redundant
200
+		if proxiedIP != nil && !proxiedIP.Equal(utils.AddrToIP(pConn.RemoteAddr())) {
201
+			pConn.ProxiedIP = proxiedIP
202
+		}
203
+	}
204
+
205
+	// avoid a DoS attack from buffering excessively large messages:
206
+	conn.SetReadLimit(maxReadQBytes)
207
+
208
+	go wl.server.RunClient(NewIRCWSConn(conn))
209
+}

+ 23
- 204
irc/server.go Просмотреть файл

7
 
7
 
8
 import (
8
 import (
9
 	"bufio"
9
 	"bufio"
10
-	"crypto/tls"
11
 	"fmt"
10
 	"fmt"
12
 	"net"
11
 	"net"
13
 	"net/http"
12
 	"net/http"
53
 	throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect."
52
 	throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect."
54
 )
53
 )
55
 
54
 
56
-// ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
57
-type ListenerWrapper struct {
58
-	// protects atomic update of config and shouldStop:
59
-	sync.Mutex // tier 1
60
-	listener   net.Listener
61
-	// optional WebSocket endpoint
62
-	httpServer *http.Server
63
-	config     listenerConfig
64
-	shouldStop bool
65
-}
66
-
67
 // Server is the main Oragono server.
55
 // Server is the main Oragono server.
68
 type Server struct {
56
 type Server struct {
69
 	accounts          AccountManager
57
 	accounts          AccountManager
77
 	dlines            *DLineManager
65
 	dlines            *DLineManager
78
 	helpIndexManager  HelpIndexManager
66
 	helpIndexManager  HelpIndexManager
79
 	klines            *KLineManager
67
 	klines            *KLineManager
80
-	listeners         map[string]*ListenerWrapper
68
+	listeners         map[string]IRCListener
81
 	logger            *logger.Manager
69
 	logger            *logger.Manager
82
 	monitorManager    MonitorManager
70
 	monitorManager    MonitorManager
83
 	name              string
71
 	name              string
105
 	}
93
 	}
106
 )
94
 )
107
 
95
 
108
-type clientConn struct {
109
-	Conn   net.Conn
110
-	Config listenerConfig
111
-}
112
-
113
 // NewServer returns a new Oragono server.
96
 // NewServer returns a new Oragono server.
114
 func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
97
 func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
115
 	// initialize data structures
98
 	// initialize data structures
116
 	server := &Server{
99
 	server := &Server{
117
 		ctime:        time.Now().UTC(),
100
 		ctime:        time.Now().UTC(),
118
-		listeners:    make(map[string]*ListenerWrapper),
101
+		listeners:    make(map[string]IRCListener),
119
 		logger:       logger,
102
 		logger:       logger,
120
 		rehashSignal: make(chan os.Signal, 1),
103
 		rehashSignal: make(chan os.Signal, 1),
121
 		signals:      make(chan os.Signal, len(ServerExitSignals)),
104
 		signals:      make(chan os.Signal, len(ServerExitSignals)),
223
 	}
206
 	}
224
 }
207
 }
225
 
208
 
226
-//
227
-// IRC protocol listeners
228
-//
229
-
230
-// createListener starts a given listener.
231
-func (server *Server) createListener(addr string, conf listenerConfig, bindMode os.FileMode) (*ListenerWrapper, error) {
232
-	if conf.WebSocket {
233
-		return server.createWSListener(addr, conf)
234
-	}
235
-	return server.createNetListener(addr, conf, bindMode)
236
-}
237
-
238
-func (server *Server) isTrusted(ip string) bool {
239
-	netIP := net.ParseIP(ip)
240
-	return utils.IPInNets(netIP, server.Config().Server.proxyAllowedFromNets)
241
-}
242
-
243
-func (server *Server) followHTTPForwards(addr string, forwards string) string {
244
-	if !server.isTrusted(addr) {
245
-		return addr
246
-	}
247
-
248
-	forwardIPs := strings.Split(forwards, ",")
249
-
250
-	// Iterate backwards to have the inner-most proxy first.
251
-	for i := len(forwardIPs) - 1; i >= 0; i-- {
252
-		// Using i so that addr points to the last item after the end of the loop.
253
-		addr = forwardIPs[i]
254
-
255
-		if !server.isTrusted(addr) {
256
-			return addr
257
-		}
258
-	}
259
-
260
-	// All IPs are trusted? weird. Let's take the last one and call it a day.
261
-	return addr
262
-}
263
-
264
-// createWSListener starts a given WebSocket listener.
265
-func (server *Server) createWSListener(addr string, conf listenerConfig) (*ListenerWrapper, error) {
266
-	var listener net.Listener
267
-	var err error
268
-
269
-	handler := func(w http.ResponseWriter, r *http.Request) {
270
-		remoteAddr := r.RemoteAddr
271
-		if header, ok := r.Header["X-Forwarded-For"]; ok {
272
-			remoteAddr = server.followHTTPForwards(remoteAddr, header[len(header)-1])
273
-		}
274
-
275
-		conn, err := wsUpgrader.Upgrade(w, r, nil)
276
-		if err != nil {
277
-			server.logger.Error("internal", "upgrade error", addr, err.Error())
278
-			return
279
-		}
280
-
281
-		newConn := clientConn{
282
-			Conn:   WSContainer{conn},
283
-			Config: conf,
284
-		}
285
-
286
-		server.RunClient(newConn, "")
287
-	}
288
-	endpoint := http.Server{
289
-		Addr:           addr,
290
-		Handler:        http.HandlerFunc(handler),
291
-		ReadTimeout:    10 * time.Second,
292
-		WriteTimeout:   10 * time.Second,
293
-		MaxHeaderBytes: 1 << 20,
294
-	}
295
-	if conf.TLSConfig != nil {
296
-		listener, err = tls.Listen("tcp", addr, conf.TLSConfig)
297
-	} else {
298
-		listener, err = net.Listen("tcp", addr)
299
-	}
300
-	if err != nil {
301
-		return nil, err
302
-	}
303
-
304
-	// throw our details to the server so we can be modified/killed later
305
-	wrapper := ListenerWrapper{
306
-		listener:   listener,
307
-		httpServer: &endpoint,
308
-		config:     conf,
309
-		shouldStop: false,
310
-	}
311
-
312
-	go func() {
313
-		err := endpoint.Serve(listener)
314
-		if err != nil {
315
-			server.logger.Error("internal", "Failed to start WebSocket listener on", addr)
316
-		}
317
-	}()
318
-
319
-	return &wrapper, nil
320
-}
321
-
322
-// createNetListener starts a given unix or TCP listener.
323
-func (server *Server) createNetListener(addr string, conf listenerConfig, bindMode os.FileMode) (*ListenerWrapper, error) {
324
-	var listener net.Listener
325
-	var err error
326
-
327
-	addr = strings.TrimPrefix(addr, "unix:")
328
-	if strings.HasPrefix(addr, "/") {
329
-		// https://stackoverflow.com/a/34881585
330
-		os.Remove(addr)
331
-		listener, err = net.Listen("unix", addr)
332
-		if err == nil && bindMode != 0 {
333
-			os.Chmod(addr, bindMode)
334
-		}
335
-	} else {
336
-		listener, err = net.Listen("tcp", addr)
337
-	}
338
-	if err != nil {
339
-		return nil, err
340
-	}
341
-
342
-	// throw our details to the server so we can be modified/killed later
343
-	wrapper := ListenerWrapper{
344
-		listener:   listener,
345
-		config:     conf,
346
-		shouldStop: false,
347
-	}
348
-
349
-	var shouldStop bool
350
-
351
-	// setup accept goroutine
352
-	go func() {
353
-		for {
354
-			conn, err := listener.Accept()
355
-
356
-			// synchronously access config data:
357
-			wrapper.Lock()
358
-			shouldStop = wrapper.shouldStop
359
-			conf := wrapper.config
360
-			wrapper.Unlock()
361
-
362
-			if shouldStop {
363
-				if conn != nil {
364
-					conn.Close()
365
-				}
366
-				listener.Close()
367
-				return
368
-			} else if err == nil {
369
-				var proxyLine string
370
-				if conf.ProxyBeforeTLS {
371
-					proxyLine = readRawProxyLine(conn)
372
-					if proxyLine == "" {
373
-						server.logger.Error("internal", "bad TLS-proxy line from", addr)
374
-						conn.Close()
375
-						continue
376
-					}
377
-				}
378
-				if conf.TLSConfig != nil {
379
-					conn = tls.Server(conn, conf.TLSConfig)
380
-				}
381
-				newConn := clientConn{
382
-					Conn:   conn,
383
-					Config: conf,
384
-				}
385
-				// hand off the connection
386
-				go server.RunClient(newConn, proxyLine)
387
-			} else {
388
-				server.logger.Error("internal", "accept error", addr, err.Error())
389
-			}
390
-		}
391
-	}()
392
-
393
-	return &wrapper, nil
394
-}
395
-
396
 //
209
 //
397
 // server functionality
210
 // server functionality
398
 //
211
 //
911
 }
724
 }
912
 
725
 
913
 func (server *Server) setupListeners(config *Config) (err error) {
726
 func (server *Server) setupListeners(config *Config) (err error) {
914
-	logListener := func(addr string, config listenerConfig) {
727
+	logListener := func(addr string, config utils.ListenerConfig) {
915
 		server.logger.Info("listeners",
728
 		server.logger.Info("listeners",
916
-			fmt.Sprintf("now listening on %s, tls=%t, tlsproxy=%t, tor=%t, websocket=%t.", addr, (config.TLSConfig != nil), config.ProxyBeforeTLS, config.Tor, config.WebSocket),
729
+			fmt.Sprintf("now listening on %s, tls=%t, tlsproxy=%t, tor=%t, websocket=%t.", addr, (config.TLSConfig != nil), config.RequireProxy, config.Tor, config.WebSocket),
917
 		)
730
 		)
918
 	}
731
 	}
919
 
732
 
922
 		currentListener := server.listeners[addr]
735
 		currentListener := server.listeners[addr]
923
 		newConfig, stillConfigured := config.Server.trueListeners[addr]
736
 		newConfig, stillConfigured := config.Server.trueListeners[addr]
924
 
737
 
925
-		currentListener.Lock()
926
-		currentListener.shouldStop = !stillConfigured
927
-		currentListener.config = newConfig
928
-		currentListener.Unlock()
929
-
930
 		if stillConfigured {
738
 		if stillConfigured {
739
+			err := currentListener.Reload(newConfig)
740
+			// attempt to stop and replace the listener if the reload failed
741
+			if err != nil {
742
+				currentListener.Stop()
743
+				newListener, err := NewListener(server, addr, newConfig, config.Server.UnixBindMode)
744
+				if err != nil {
745
+					delete(server.listeners, addr)
746
+					return err
747
+				} else {
748
+					server.listeners[addr] = newListener
749
+				}
750
+			}
931
 			logListener(addr, newConfig)
751
 			logListener(addr, newConfig)
932
 		} else {
752
 		} else {
933
-			// tell the listener it should stop by interrupting its Accept() call:
934
-			currentListener.listener.Close()
753
+			currentListener.Stop()
935
 			delete(server.listeners, addr)
754
 			delete(server.listeners, addr)
936
 			server.logger.Info("listeners", fmt.Sprintf("stopped listening on %s.", addr))
755
 			server.logger.Info("listeners", fmt.Sprintf("stopped listening on %s.", addr))
937
 		}
756
 		}
945
 		}
764
 		}
946
 		_, exists := server.listeners[newAddr]
765
 		_, exists := server.listeners[newAddr]
947
 		if !exists {
766
 		if !exists {
948
-			// make new listener
949
-			listener, listenerErr := server.createListener(newAddr, newConfig, config.Server.UnixBindMode)
950
-			if listenerErr != nil {
767
+			// make a new listener
768
+			newListener, listenerErr := NewListener(server, newAddr, newConfig, config.Server.UnixBindMode)
769
+			if err != nil {
951
 				server.logger.Error("server", "couldn't listen on", newAddr, listenerErr.Error())
770
 				server.logger.Error("server", "couldn't listen on", newAddr, listenerErr.Error())
952
 				err = listenerErr
771
 				err = listenerErr
953
-				continue
772
+			} else {
773
+				server.listeners[newAddr] = newListener
774
+				logListener(newAddr, newConfig)
954
 			}
775
 			}
955
-			server.listeners[newAddr] = listener
956
-			logListener(newAddr, newConfig)
957
 		}
776
 		}
958
 	}
777
 	}
959
 
778
 

+ 5
- 45
irc/socket.go Просмотреть файл

5
 package irc
5
 package irc
6
 
6
 
7
 import (
7
 import (
8
-	"bufio"
9
-	"crypto/sha256"
10
-	"crypto/tls"
11
-	"encoding/hex"
12
 	"errors"
8
 	"errors"
13
 	"io"
9
 	"io"
14
-	"net"
15
 	"strings"
10
 	"strings"
16
 	"sync"
11
 	"sync"
17
-	"time"
18
 
12
 
19
 	"github.com/oragono/oragono/irc/utils"
13
 	"github.com/oragono/oragono/irc/utils"
20
 )
14
 )
21
 
15
 
22
 var (
16
 var (
23
-	handshakeTimeout = RegisterTimeout
24
 	errSendQExceeded = errors.New("SendQ exceeded")
17
 	errSendQExceeded = errors.New("SendQ exceeded")
25
 
18
 
26
 	sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
19
 	sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
30
 type Socket struct {
23
 type Socket struct {
31
 	sync.Mutex
24
 	sync.Mutex
32
 
25
 
33
-	conn   net.Conn
34
-	reader *bufio.Reader
26
+	conn IRCConn
35
 
27
 
36
 	maxSendQBytes int
28
 	maxSendQBytes int
37
 
29
 
47
 }
39
 }
48
 
40
 
49
 // NewSocket returns a new Socket.
41
 // NewSocket returns a new Socket.
50
-func NewSocket(conn net.Conn, maxReadQBytes int, maxSendQBytes int) *Socket {
42
+func NewSocket(conn IRCConn, maxSendQBytes int) *Socket {
51
 	result := Socket{
43
 	result := Socket{
52
 		conn:          conn,
44
 		conn:          conn,
53
-		reader:        bufio.NewReaderSize(conn, maxReadQBytes),
54
 		maxSendQBytes: maxSendQBytes,
45
 		maxSendQBytes: maxSendQBytes,
55
 	}
46
 	}
56
 	result.writerSemaphore.Initialize(1)
47
 	result.writerSemaphore.Initialize(1)
66
 	socket.wakeWriter()
57
 	socket.wakeWriter()
67
 }
58
 }
68
 
59
 
69
-// CertFP returns the fingerprint of the certificate provided by the client.
70
-func (socket *Socket) CertFP() (string, error) {
71
-	var tlsConn, isTLS = socket.conn.(*tls.Conn)
72
-	if !isTLS {
73
-		return "", errNotTLS
74
-	}
75
-
76
-	// ensure handehake is performed, and timeout after a few seconds
77
-	tlsConn.SetDeadline(time.Now().Add(handshakeTimeout))
78
-	err := tlsConn.Handshake()
79
-	tlsConn.SetDeadline(time.Time{})
80
-
81
-	if err != nil {
82
-		return "", err
83
-	}
84
-
85
-	peerCerts := tlsConn.ConnectionState().PeerCertificates
86
-	if len(peerCerts) < 1 {
87
-		return "", errNoPeerCerts
88
-	}
89
-
90
-	rawCert := sha256.Sum256(peerCerts[0].Raw)
91
-	fingerprint := hex.EncodeToString(rawCert[:])
92
-
93
-	return fingerprint, nil
94
-}
95
-
96
 // Read returns a single IRC line from a Socket.
60
 // Read returns a single IRC line from a Socket.
97
 func (socket *Socket) Read() (string, error) {
61
 func (socket *Socket) Read() (string, error) {
98
 	if socket.IsClosed() {
62
 	if socket.IsClosed() {
99
 		return "", io.EOF
63
 		return "", io.EOF
100
 	}
64
 	}
101
 
65
 
102
-	lineBytes, isPrefix, err := socket.reader.ReadLine()
103
-	if isPrefix {
104
-		return "", errReadQ
105
-	}
66
+	lineBytes, err := socket.conn.ReadLine()
106
 
67
 
107
 	// convert bytes to string
68
 	// convert bytes to string
108
 	line := string(lineBytes)
69
 	line := string(lineBytes)
183
 		return io.EOF
144
 		return io.EOF
184
 	}
145
 	}
185
 
146
 
186
-	_, err = socket.conn.Write(data)
147
+	err = socket.conn.Write(data)
187
 	if err != nil {
148
 	if err != nil {
188
 		socket.finalize()
149
 		socket.finalize()
189
 	}
150
 	}
255
 
216
 
256
 	var err error
217
 	var err error
257
 	if 0 < len(buffers) {
218
 	if 0 < len(buffers) {
258
-		// on Linux, the runtime will optimize this into a single writev(2) call:
259
-		_, err = (*net.Buffers)(&buffers).WriteTo(socket.conn)
219
+		socket.conn.WriteBuffers(buffers)
260
 	}
220
 	}
261
 
221
 
262
 	closed = closed || err != nil
222
 	closed = closed || err != nil

+ 34
- 0
irc/utils/crypto.go Просмотреть файл

5
 
5
 
6
 import (
6
 import (
7
 	"crypto/rand"
7
 	"crypto/rand"
8
+	"crypto/sha256"
8
 	"crypto/subtle"
9
 	"crypto/subtle"
10
+	"crypto/tls"
9
 	"encoding/base32"
11
 	"encoding/base32"
10
 	"encoding/base64"
12
 	"encoding/base64"
11
 	"encoding/hex"
13
 	"encoding/hex"
12
 	"errors"
14
 	"errors"
15
+	"net"
13
 	"strings"
16
 	"strings"
17
+	"time"
14
 )
18
 )
15
 
19
 
16
 var (
20
 var (
18
 	B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
22
 	B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
19
 
23
 
20
 	ErrInvalidCertfp = errors.New("Invalid certfp")
24
 	ErrInvalidCertfp = errors.New("Invalid certfp")
25
+
26
+	ErrNoPeerCerts = errors.New("No certfp available")
27
+
28
+	ErrNotTLS = errors.New("Connection is not TLS")
21
 )
29
 )
22
 
30
 
23
 const (
31
 const (
83
 	}
91
 	}
84
 	return
92
 	return
85
 }
93
 }
94
+
95
+func GetCertFP(conn net.Conn, handshakeTimeout time.Duration) (result string, err error) {
96
+	tlsConn, isTLS := conn.(*tls.Conn)
97
+	if !isTLS {
98
+		return "", ErrNotTLS
99
+	}
100
+
101
+	// ensure handshake is performed
102
+	tlsConn.SetDeadline(time.Now().Add(handshakeTimeout))
103
+	err = tlsConn.Handshake()
104
+	tlsConn.SetDeadline(time.Time{})
105
+
106
+	if err != nil {
107
+		return "", err
108
+	}
109
+
110
+	peerCerts := tlsConn.ConnectionState().PeerCertificates
111
+	if len(peerCerts) < 1 {
112
+		return "", ErrNoPeerCerts
113
+	}
114
+
115
+	rawCert := sha256.Sum256(peerCerts[0].Raw)
116
+	fingerprint := hex.EncodeToString(rawCert[:])
117
+
118
+	return fingerprint, nil
119
+}

+ 30
- 0
irc/utils/glob.go Просмотреть файл

1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package utils
5
+
6
+import (
7
+	"bytes"
8
+	"regexp"
9
+	"strings"
10
+)
11
+
12
+// yet another glob implementation in Go
13
+
14
+func CompileGlob(glob string) (result *regexp.Regexp, err error) {
15
+	var buf bytes.Buffer
16
+	buf.WriteByte('^')
17
+	for {
18
+		i := strings.IndexByte(glob, '*')
19
+		if i == -1 {
20
+			buf.WriteString(regexp.QuoteMeta(glob))
21
+			break
22
+		} else {
23
+			buf.WriteString(regexp.QuoteMeta(glob[:i]))
24
+			buf.WriteString(".*")
25
+			glob = glob[i+1:]
26
+		}
27
+	}
28
+	buf.WriteByte('$')
29
+	return regexp.Compile(buf.String())
30
+}

+ 37
- 0
irc/utils/glob_test.go Просмотреть файл

1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package utils
5
+
6
+import (
7
+	"regexp"
8
+	"testing"
9
+)
10
+
11
+func globMustCompile(glob string) *regexp.Regexp {
12
+	re, err := CompileGlob(glob)
13
+	if err != nil {
14
+		panic(err)
15
+	}
16
+	return re
17
+}
18
+
19
+func assertMatches(glob, str string, match bool, t *testing.T) {
20
+	re := globMustCompile(glob)
21
+	if re.MatchString(str) != match {
22
+		t.Errorf("should %s match %s? %t, but got %t instead", glob, str, match, !match)
23
+	}
24
+}
25
+
26
+func TestGlob(t *testing.T) {
27
+	assertMatches("https://testnet.oragono.io", "https://testnet.oragono.io", true, t)
28
+	assertMatches("https://*.oragono.io", "https://testnet.oragono.io", true, t)
29
+	assertMatches("*://*.oragono.io", "https://testnet.oragono.io", true, t)
30
+	assertMatches("*://*.oragono.io", "https://oragono.io", false, t)
31
+	assertMatches("*://*.oragono.io", "https://githubusercontent.com", false, t)
32
+
33
+	assertMatches("", "", true, t)
34
+	assertMatches("", "x", false, t)
35
+	assertMatches("*", "", true, t)
36
+	assertMatches("*", "x", true, t)
37
+}

+ 42
- 7
irc/utils/net.go Просмотреть файл

22
 func AddrToIP(addr net.Addr) net.IP {
22
 func AddrToIP(addr net.Addr) net.IP {
23
 	if tcpaddr, ok := addr.(*net.TCPAddr); ok {
23
 	if tcpaddr, ok := addr.(*net.TCPAddr); ok {
24
 		return tcpaddr.IP.To16()
24
 		return tcpaddr.IP.To16()
25
-	} else if AddrIsUnix(addr) {
25
+	} else if _, ok := addr.(*net.UnixAddr); ok {
26
 		return IPv4LoopbackAddress
26
 		return IPv4LoopbackAddress
27
 	} else {
27
 	} else {
28
 		return nil
28
 		return nil
29
 	}
29
 	}
30
 }
30
 }
31
 
31
 
32
-// AddrIsUnix returns whether the address is a unix domain socket.
33
-func AddrIsUnix(addr net.Addr) bool {
34
-	_, ok := addr.(*net.UnixAddr)
35
-	return ok
36
-}
37
-
38
 // IPStringToHostname converts a string representation of an IP address to an IRC-ready hostname
32
 // IPStringToHostname converts a string representation of an IP address to an IRC-ready hostname
39
 func IPStringToHostname(ipStr string) string {
33
 func IPStringToHostname(ipStr string) string {
40
 	if 0 < len(ipStr) && ipStr[0] == ':' {
34
 	if 0 < len(ipStr) && ipStr[0] == ':' {
158
 	}
152
 	}
159
 	return
153
 	return
160
 }
154
 }
155
+
156
+// Process the X-Forwarded-For header, validating against a list of trusted IPs.
157
+// Returns the address that the request was forwarded for, or nil if no trustworthy
158
+// data was available.
159
+func HandleXForwardedFor(remoteAddr string, xForwardedFor string, whitelist []net.IPNet) (result net.IP) {
160
+	// http.Request.RemoteAddr "has no defined format". with TCP it's typically "127.0.0.1:23784",
161
+	// with unix domain it's typically "@"
162
+	var remoteIP net.IP
163
+	host, _, err := net.SplitHostPort(remoteAddr)
164
+	if err != nil {
165
+		remoteIP = IPv4LoopbackAddress
166
+	} else {
167
+		remoteIP = net.ParseIP(host)
168
+	}
169
+
170
+	if remoteIP == nil || !IPInNets(remoteIP, whitelist) {
171
+		return remoteIP
172
+	}
173
+
174
+	// walk backwards through the X-Forwarded-For chain looking for an IP
175
+	// that is *not* trusted. that means it was added by one of our trusted
176
+	// forwarders (either remoteIP or something ahead of it in the chain)
177
+	// and we can trust it:
178
+	result = remoteIP
179
+	forwardedIPs := strings.Split(xForwardedFor, ",")
180
+	for i := len(forwardedIPs) - 1; i >= 0; i-- {
181
+		proxiedIP := net.ParseIP(strings.TrimSpace(forwardedIPs[i]))
182
+		if proxiedIP == nil {
183
+			return
184
+		} else if !IPInNets(proxiedIP, whitelist) {
185
+			return proxiedIP
186
+		} else {
187
+			result = proxiedIP
188
+		}
189
+	}
190
+
191
+	// no valid untrusted IPs were found in the chain;
192
+	// return either the last valid and trusted IP (which must be the origin),
193
+	// or nil:
194
+	return
195
+}

+ 36
- 0
irc/utils/net_test.go Просмотреть файл

159
 	assertEqual(NetToNormalizedString(network), "2001:db8::1", t)
159
 	assertEqual(NetToNormalizedString(network), "2001:db8::1", t)
160
 	assertEqual(network.Contains(net.ParseIP("2001:0db8::1")), true, t)
160
 	assertEqual(network.Contains(net.ParseIP("2001:0db8::1")), true, t)
161
 }
161
 }
162
+
163
+func checkXFF(remoteAddr, forwardedHeader string, expectedStr string, t *testing.T) {
164
+	whitelistCIDRs := []string{"10.0.0.0/8", "127.0.0.1/8"}
165
+	var whitelist []net.IPNet
166
+	for _, str := range whitelistCIDRs {
167
+		_, wlNet, err := net.ParseCIDR(str)
168
+		if err != nil {
169
+			panic(err)
170
+		}
171
+		whitelist = append(whitelist, *wlNet)
172
+	}
173
+
174
+	expected := net.ParseIP(expectedStr)
175
+	actual := HandleXForwardedFor(remoteAddr, forwardedHeader, whitelist)
176
+
177
+	if !actual.Equal(expected) {
178
+		t.Errorf("handling %s and %s, expected %s, got %s", remoteAddr, forwardedHeader, expected, actual)
179
+	}
180
+}
181
+
182
+func TestXForwardedFor(t *testing.T) {
183
+	checkXFF("8.8.4.4:9999", "", "8.8.4.4", t)
184
+	// forged XFF header from untrustworthy external IP, should be ignored:
185
+	checkXFF("8.8.4.4:9999", "1.1.1.1", "8.8.4.4", t)
186
+
187
+	checkXFF("10.0.0.4:28432", "", "10.0.0.4", t)
188
+
189
+	checkXFF("10.0.0.4:28432", "8.8.4.4", "8.8.4.4", t)
190
+	checkXFF("10.0.0.4:28432", "10.0.0.3", "10.0.0.3", t)
191
+
192
+	checkXFF("10.0.0.4:28432", "1.1.1.1, 8.8.4.4", "8.8.4.4", t)
193
+	checkXFF("10.0.0.4:28432", "8.8.4.4, 1.1.1.1, 10.0.0.3", "1.1.1.1", t)
194
+	checkXFF("10.0.0.4:28432", "10.0.0.1, 10.0.0.2, 10.0.0.3", "10.0.0.1", t)
195
+
196
+	checkXFF("@", "8.8.4.4, 1.1.1.1, 10.0.0.3", "1.1.1.1", t)
197
+}

+ 174
- 0
irc/utils/proxy.go Просмотреть файл

1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package utils
5
+
6
+import (
7
+	"crypto/tls"
8
+	"errors"
9
+	"net"
10
+	"strings"
11
+	"sync"
12
+	"time"
13
+)
14
+
15
+// TODO: handle PROXY protocol v2 (the binary protocol)
16
+
17
+const (
18
+	// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
19
+	// "a 108-byte buffer is always enough to store all the line and a trailing zero
20
+	// for string processing."
21
+	maxProxyLineLen = 107
22
+)
23
+
24
+var (
25
+	ErrBadProxyLine = errors.New("invalid PROXY line")
26
+	// TODO(golang/go#4373): replace this with the stdlib ErrNetClosing
27
+	ErrNetClosing = errors.New("use of closed network connection")
28
+)
29
+
30
+// ListenerConfig is all the information about how to process
31
+// incoming IRC connections on a listener.
32
+type ListenerConfig struct {
33
+	TLSConfig     *tls.Config
34
+	ProxyDeadline time.Duration
35
+	RequireProxy  bool
36
+	// these are just metadata for easier tracking,
37
+	// they are not used by ReloadableListener:
38
+	Tor       bool
39
+	STSOnly   bool
40
+	WebSocket bool
41
+}
42
+
43
+// read a PROXY line one byte at a time, to ensure we don't read anything beyond
44
+// that into a buffer, which would break the TLS handshake
45
+func readRawProxyLine(conn net.Conn, deadline time.Duration) (result string) {
46
+	// normally this is covered by ping timeouts, but we're doing this outside
47
+	// of the normal client goroutine:
48
+	conn.SetDeadline(time.Now().Add(deadline))
49
+	defer conn.SetDeadline(time.Time{})
50
+
51
+	var buf [maxProxyLineLen]byte
52
+	oneByte := make([]byte, 1)
53
+	i := 0
54
+	for i < maxProxyLineLen {
55
+		n, err := conn.Read(oneByte)
56
+		if err != nil {
57
+			return
58
+		} else if n == 1 {
59
+			buf[i] = oneByte[0]
60
+			if buf[i] == '\n' {
61
+				candidate := string(buf[0 : i+1])
62
+				if strings.HasPrefix(candidate, "PROXY") {
63
+					return candidate
64
+				} else {
65
+					return
66
+				}
67
+			}
68
+			i += 1
69
+		}
70
+	}
71
+
72
+	// no \r\n, fail out
73
+	return
74
+}
75
+
76
+// ParseProxyLine parses a PROXY protocol (v1) line and returns the remote IP.
77
+func ParseProxyLine(line string) (ip net.IP, err error) {
78
+	params := strings.Fields(line)
79
+	if len(params) != 6 || params[0] != "PROXY" {
80
+		return nil, ErrBadProxyLine
81
+	}
82
+	ip = net.ParseIP(params[2])
83
+	if ip == nil {
84
+		return nil, ErrBadProxyLine
85
+	}
86
+	return ip.To16(), nil
87
+}
88
+
89
+/// ProxiedConnection is a net.Conn with some additional data stapled to it;
90
+// the proxied IP, if one was read via the PROXY protocol, and the listener
91
+// configuration.
92
+type ProxiedConnection struct {
93
+	net.Conn
94
+	ProxiedIP net.IP
95
+	Config    ListenerConfig
96
+}
97
+
98
+// ReloadableListener is a wrapper for net.Listener that allows reloading
99
+// of config data for postprocessing connections (TLS, PROXY protocol, etc.)
100
+type ReloadableListener struct {
101
+	// TODO: make this lock-free
102
+	sync.Mutex
103
+	realListener net.Listener
104
+	config       ListenerConfig
105
+	isClosed     bool
106
+}
107
+
108
+func NewReloadableListener(realListener net.Listener, config ListenerConfig) *ReloadableListener {
109
+	return &ReloadableListener{
110
+		realListener: realListener,
111
+		config:       config,
112
+	}
113
+}
114
+
115
+func (rl *ReloadableListener) Reload(config ListenerConfig) {
116
+	rl.Lock()
117
+	rl.config = config
118
+	rl.Unlock()
119
+}
120
+
121
+func (rl *ReloadableListener) Accept() (conn net.Conn, err error) {
122
+	conn, err = rl.realListener.Accept()
123
+
124
+	rl.Lock()
125
+	config := rl.config
126
+	isClosed := rl.isClosed
127
+	rl.Unlock()
128
+
129
+	if isClosed {
130
+		if err == nil {
131
+			conn.Close()
132
+		}
133
+		err = ErrNetClosing
134
+	}
135
+	if err != nil {
136
+		return nil, err
137
+	}
138
+
139
+	var proxiedIP net.IP
140
+	if config.RequireProxy {
141
+		// this will occur synchronously on the goroutine calling Accept(),
142
+		// but that's OK because this listener *requires* a PROXY line,
143
+		// therefore it must be used with proxies that always send the line
144
+		// and we won't get slowloris'ed waiting for the client response
145
+		proxyLine := readRawProxyLine(conn, config.ProxyDeadline)
146
+		proxiedIP, err = ParseProxyLine(proxyLine)
147
+		if err != nil {
148
+			conn.Close()
149
+			return nil, err
150
+		}
151
+	}
152
+
153
+	if config.TLSConfig != nil {
154
+		conn = tls.Server(conn, config.TLSConfig)
155
+	}
156
+
157
+	return &ProxiedConnection{
158
+		Conn:      conn,
159
+		ProxiedIP: proxiedIP,
160
+		Config:    config,
161
+	}, nil
162
+}
163
+
164
+func (rl *ReloadableListener) Close() error {
165
+	rl.Lock()
166
+	rl.isClosed = true
167
+	rl.Unlock()
168
+
169
+	return rl.realListener.Close()
170
+}
171
+
172
+func (rl *ReloadableListener) Addr() net.Addr {
173
+	return rl.realListener.Addr()
174
+}

+ 0
- 70
irc/websocket.go Просмотреть файл

1
-package irc
2
-
3
-import (
4
-	"bytes"
5
-	"errors"
6
-	"github.com/gorilla/websocket"
7
-	"net/http"
8
-	"time"
9
-	"unicode/utf8"
10
-)
11
-
12
-var wsUpgrader = websocket.Upgrader{
13
-	ReadBufferSize:  2 * 1024,
14
-	WriteBufferSize: 2 * 1024,
15
-	// If a WS session contains sensitive information, and you choose to use
16
-	// cookies for authentication (during the HTTP(S) upgrade request), then
17
-	// you should check that Origin is a domain under your control. If it
18
-	// isn't, then it is possible for users of your site, visiting a naughty
19
-	// Origin, to have a WS opened using their credentials. See
20
-	// http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html#main.
21
-	// We don't care about Origin because the (IRC) authentication is contained
22
-	// in the WS stream -- the WS session is not privileged when it is opened.
23
-	CheckOrigin: func(r *http.Request) bool { return true },
24
-}
25
-
26
-// WSContainer wraps a WebSocket connection so that it implements net.Conn
27
-// entirely.
28
-type WSContainer struct {
29
-	*websocket.Conn
30
-}
31
-
32
-func (ws WSContainer) Read(b []byte) (n int, err error) {
33
-	var messageType int
34
-	var bytes []byte
35
-
36
-	for {
37
-		messageType, bytes, err = ws.ReadMessage()
38
-		if messageType == websocket.TextMessage {
39
-			n = copy(b, bytes)
40
-			return
41
-		}
42
-		if len(bytes) == 0 {
43
-			return 0, nil
44
-		}
45
-		// Throw other kind of messages away.
46
-	}
47
-	// We don't want to return (0, nil) here because that would mean the
48
-	// connection is closed (Read calls must block until data is received).
49
-}
50
-
51
-func (ws WSContainer) Write(b []byte) (n int, err error) {
52
-	if !utf8.Valid(b) {
53
-		return 0, errors.New("outgoing WebSocket message isn't valid UTF-8")
54
-	}
55
-
56
-	b = bytes.TrimSuffix(b, []byte("\r\n"))
57
-	n = len(b)
58
-	err = ws.WriteMessage(websocket.TextMessage, b)
59
-	return
60
-}
61
-
62
-// SetDeadline is part of the net.Conn interface.
63
-func (ws WSContainer) SetDeadline(t time.Time) (err error) {
64
-	err = ws.SetWriteDeadline(t)
65
-	if err != nil {
66
-		return
67
-	}
68
-	err = ws.SetReadDeadline(t)
69
-	return
70
-}

+ 15
- 3
oragono.yaml Просмотреть файл

61
         # "/hidden_service_sockets/oragono_tor_sock":
61
         # "/hidden_service_sockets/oragono_tor_sock":
62
         #     tor: true
62
         #     tor: true
63
 
63
 
64
-        # Example of a WebSocket listener.
65
-        #"127.0.0.1:8080":
66
-        #    websocket: true
64
+        # Example of a WebSocket listener:
65
+        # ":4430":
66
+        #     websocket: true
67
+        #     tls:
68
+        #         key: tls.key
69
+        #         cert: tls.crt
67
 
70
 
68
     # sets the permissions for Unix listen sockets. on a typical Linux system,
71
     # sets the permissions for Unix listen sockets. on a typical Linux system,
69
     # the default is 0775 or 0755, which prevents other users/groups from connecting
72
     # the default is 0775 or 0755, which prevents other users/groups from connecting
106
         # should clients include this STS policy when they ship their inbuilt preload lists?
109
         # should clients include this STS policy when they ship their inbuilt preload lists?
107
         preload: false
110
         preload: false
108
 
111
 
112
+    websockets:
113
+        # sets the Origin headers that will be accepted for websocket connections.
114
+        # an empty list means any value (or no value) is allowed. the main use of this
115
+        # is to prevent malicious third-party Javascript from co-opting non-malicious
116
+        # clients (i.e., mainstream browsers) to DDoS your server.
117
+        allowed-origins:
118
+            # - "https://oragono.io"
119
+            # - "https://*.oragono.io"
120
+
109
     # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
121
     # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
110
     # channel names, account names, etc.), and how they are normalized for case.
122
     # channel names, account names, etc.), and how they are normalized for case.
111
     # with the recommended default of 'precis', utf-8 identifiers that are "sane"
123
     # with the recommended default of 'precis', utf-8 identifiers that are "sane"

Загрузка…
Отмена
Сохранить