瀏覽代碼

scripting API for IP bans

See discussion on #68.
tags/v2.4.0-rc1
Shivaram Lingamneni 3 年之前
父節點
當前提交
1a98a37a75
共有 10 個檔案被更改,包括 267 行新增101 行删除
  1. 18
    0
      conventional.yaml
  2. 18
    0
      default.yaml
  3. 2
    2
      irc/accounts.go
  4. 54
    63
      irc/authscript.go
  5. 10
    5
      irc/client.go
  6. 14
    8
      irc/config.go
  7. 2
    1
      irc/gateways.go
  8. 83
    0
      irc/script.go
  9. 2
    0
      irc/semaphores.go
  10. 64
    22
      irc/server.go

+ 18
- 0
conventional.yaml 查看文件

243
             #     max-concurrent-connections: 128
243
             #     max-concurrent-connections: 128
244
             #     max-connections-per-window: 1024
244
             #     max-connections-per-window: 1024
245
 
245
 
246
+    # pluggable IP ban mechanism, via subprocess invocation
247
+    # this can be used to check new connections against a DNSBL, for example
248
+    # see the manual for details on how to write an IP ban checking script
249
+    ip-check-script:
250
+        enabled: false
251
+        command: "/usr/local/bin/check-ip-ban"
252
+        # constant list of args to pass to the command; the actual query
253
+        # and result are transmitted over stdin/stdout:
254
+        args: []
255
+        # timeout for process execution, after which we send a SIGTERM:
256
+        timeout: 9s
257
+        # how long after the SIGTERM before we follow up with a SIGKILL:
258
+        kill-timeout: 1s
259
+        # how many scripts are allowed to run at once? 0 for no limit:
260
+        max-concurrency: 64
261
+
246
     # IP cloaking hides users' IP addresses from other users and from channel admins
262
     # IP cloaking hides users' IP addresses from other users and from channel admins
247
     # (but not from server admins), while still allowing channel admins to ban
263
     # (but not from server admins), while still allowing channel admins to ban
248
     # offending IP addresses or networks. In place of hostnames derived from reverse
264
     # offending IP addresses or networks. In place of hostnames derived from reverse
483
         timeout: 9s
499
         timeout: 9s
484
         # how long after the SIGTERM before we follow up with a SIGKILL:
500
         # how long after the SIGTERM before we follow up with a SIGKILL:
485
         kill-timeout: 1s
501
         kill-timeout: 1s
502
+        # how many scripts are allowed to run at once? 0 for no limit:
503
+        max-concurrency: 64
486
 
504
 
487
 # channel options
505
 # channel options
488
 channels:
506
 channels:

+ 18
- 0
default.yaml 查看文件

270
             #     max-concurrent-connections: 128
270
             #     max-concurrent-connections: 128
271
             #     max-connections-per-window: 1024
271
             #     max-connections-per-window: 1024
272
 
272
 
273
+    # pluggable IP ban mechanism, via subprocess invocation
274
+    # this can be used to check new connections against a DNSBL, for example
275
+    # see the manual for details on how to write an IP ban checking script
276
+    ip-check-script:
277
+        enabled: false
278
+        command: "/usr/local/bin/check-ip-ban"
279
+        # constant list of args to pass to the command; the actual query
280
+        # and result are transmitted over stdin/stdout:
281
+        args: []
282
+        # timeout for process execution, after which we send a SIGTERM:
283
+        timeout: 9s
284
+        # how long after the SIGTERM before we follow up with a SIGKILL:
285
+        kill-timeout: 1s
286
+        # how many scripts are allowed to run at once? 0 for no limit:
287
+        max-concurrency: 64
288
+
273
     # IP cloaking hides users' IP addresses from other users and from channel admins
289
     # IP cloaking hides users' IP addresses from other users and from channel admins
274
     # (but not from server admins), while still allowing channel admins to ban
290
     # (but not from server admins), while still allowing channel admins to ban
275
     # offending IP addresses or networks. In place of hostnames derived from reverse
291
     # offending IP addresses or networks. In place of hostnames derived from reverse
511
         timeout: 9s
527
         timeout: 9s
512
         # how long after the SIGTERM before we follow up with a SIGKILL:
528
         # how long after the SIGTERM before we follow up with a SIGKILL:
513
         kill-timeout: 1s
529
         kill-timeout: 1s
530
+        # how many scripts are allowed to run at once? 0 for no limit:
531
+        max-concurrency: 64
514
 
532
 
515
 # channel options
533
 # channel options
516
 channels:
534
 channels:

+ 2
- 2
irc/accounts.go 查看文件

1095
 	config := am.server.Config()
1095
 	config := am.server.Config()
1096
 	if config.Accounts.AuthScript.Enabled {
1096
 	if config.Accounts.AuthScript.Enabled {
1097
 		var output AuthScriptOutput
1097
 		var output AuthScriptOutput
1098
-		output, err = CheckAuthScript(config.Accounts.AuthScript,
1098
+		output, err = CheckAuthScript(am.server.semaphores.AuthScript, config.Accounts.AuthScript.ScriptConfig,
1099
 			AuthScriptInput{AccountName: accountName, Passphrase: passphrase, IP: client.IP().String()})
1099
 			AuthScriptInput{AccountName: accountName, Passphrase: passphrase, IP: client.IP().String()})
1100
 		if err != nil {
1100
 		if err != nil {
1101
 			am.server.logger.Error("internal", "failed shell auth invocation", err.Error())
1101
 			am.server.logger.Error("internal", "failed shell auth invocation", err.Error())
1494
 	config := am.server.Config()
1494
 	config := am.server.Config()
1495
 	if config.Accounts.AuthScript.Enabled {
1495
 	if config.Accounts.AuthScript.Enabled {
1496
 		var output AuthScriptOutput
1496
 		var output AuthScriptOutput
1497
-		output, err = CheckAuthScript(config.Accounts.AuthScript,
1497
+		output, err = CheckAuthScript(am.server.semaphores.AuthScript, config.Accounts.AuthScript.ScriptConfig,
1498
 			AuthScriptInput{Certfp: certfp, IP: client.IP().String()})
1498
 			AuthScriptInput{Certfp: certfp, IP: client.IP().String()})
1499
 		if err != nil {
1499
 		if err != nil {
1500
 			am.server.logger.Error("internal", "failed shell auth invocation", err.Error())
1500
 			am.server.logger.Error("internal", "failed shell auth invocation", err.Error())

+ 54
- 63
irc/authscript.go 查看文件

4
 package irc
4
 package irc
5
 
5
 
6
 import (
6
 import (
7
-	"bufio"
8
 	"encoding/json"
7
 	"encoding/json"
9
 	"fmt"
8
 	"fmt"
10
-	"io"
11
-	"os/exec"
12
-	"syscall"
13
-	"time"
9
+	"net"
10
+
11
+	"github.com/oragono/oragono/irc/utils"
14
 )
12
 )
15
 
13
 
16
 // JSON-serializable input and output types for the script
14
 // JSON-serializable input and output types for the script
27
 	Error       string `json:"error"`
25
 	Error       string `json:"error"`
28
 }
26
 }
29
 
27
 
30
-// internal tupling of output and error for passing over a channel
31
-type authScriptResponse struct {
32
-	output AuthScriptOutput
33
-	err    error
34
-}
28
+func CheckAuthScript(sem utils.Semaphore, config ScriptConfig, input AuthScriptInput) (output AuthScriptOutput, err error) {
29
+	if sem != nil {
30
+		sem.Acquire()
31
+		defer sem.Release()
32
+	}
35
 
33
 
36
-func CheckAuthScript(config AuthScriptConfig, input AuthScriptInput) (output AuthScriptOutput, err error) {
37
 	inputBytes, err := json.Marshal(input)
34
 	inputBytes, err := json.Marshal(input)
38
 	if err != nil {
35
 	if err != nil {
39
 		return
36
 		return
40
 	}
37
 	}
41
-	cmd := exec.Command(config.Command, config.Args...)
42
-	stdin, err := cmd.StdinPipe()
38
+	outBytes, err := RunScript(config.Command, config.Args, inputBytes, config.Timeout, config.KillTimeout)
43
 	if err != nil {
39
 	if err != nil {
44
 		return
40
 		return
45
 	}
41
 	}
46
-	stdout, err := cmd.StdoutPipe()
42
+	err = json.Unmarshal(outBytes, &output)
47
 	if err != nil {
43
 	if err != nil {
48
 		return
44
 		return
49
 	}
45
 	}
50
 
46
 
51
-	channel := make(chan authScriptResponse, 1)
52
-	err = cmd.Start()
53
-	if err != nil {
54
-		return
55
-	}
56
-	stdin.Write(inputBytes)
57
-	stdin.Write([]byte{'\n'})
58
-
59
-	// lots of potential race conditions here. we want to ensure that Wait()
60
-	// will be called, and will return, on the other goroutine, no matter
61
-	// where it is blocked. If it's blocked on ReadBytes(), we will kill it
62
-	// (first with SIGTERM, then with SIGKILL) and ReadBytes will return
63
-	// with EOF. If it's blocked on Wait(), then one of the kill signals
64
-	// will succeed and unblock it.
65
-	go processAuthScriptOutput(cmd, stdout, channel)
66
-	outputTimer := time.NewTimer(config.Timeout)
67
-	select {
68
-	case response := <-channel:
69
-		return response.output, response.err
70
-	case <-outputTimer.C:
47
+	if output.Error != "" {
48
+		err = fmt.Errorf("Authentication process reported error: %s", output.Error)
71
 	}
49
 	}
50
+	return
51
+}
72
 
52
 
73
-	err = errTimedOut
74
-	cmd.Process.Signal(syscall.SIGTERM)
75
-	termTimer := time.NewTimer(config.Timeout)
76
-	select {
77
-	case <-channel:
78
-		return
79
-	case <-termTimer.C:
80
-	}
53
+type IPScriptResult uint
81
 
54
 
82
-	cmd.Process.Kill()
83
-	return
55
+const (
56
+	IPNotChecked  IPScriptResult = 0
57
+	IPAccepted    IPScriptResult = 1
58
+	IPBanned      IPScriptResult = 2
59
+	IPRequireSASL IPScriptResult = 3
60
+)
61
+
62
+type IPScriptInput struct {
63
+	IP string `json:"ip"`
84
 }
64
 }
85
 
65
 
86
-func processAuthScriptOutput(cmd *exec.Cmd, stdout io.Reader, channel chan authScriptResponse) {
87
-	var response authScriptResponse
88
-	var out AuthScriptOutput
89
-
90
-	reader := bufio.NewReader(stdout)
91
-	outBytes, err := reader.ReadBytes('\n')
92
-	if err == nil {
93
-		err = json.Unmarshal(outBytes, &out)
94
-		if err == nil {
95
-			response.output = out
96
-			if out.Error != "" {
97
-				err = fmt.Errorf("Authentication process reported error: %s", out.Error)
98
-			}
99
-		}
66
+type IPScriptOutput struct {
67
+	Result     IPScriptResult `json:"result"`
68
+	BanMessage string         `json:"banMessage"`
69
+	// for caching: the network to which this result is applicable, and a TTL in seconds:
70
+	CacheNet     string `json:"cacheNet"`
71
+	CacheSeconds int    `json:"cacheSeconds"`
72
+	Error        string `json:"error"`
73
+}
74
+
75
+func CheckIPBan(sem utils.Semaphore, config ScriptConfig, addr net.IP) (output IPScriptOutput, err error) {
76
+	if sem != nil {
77
+		sem.Acquire()
78
+		defer sem.Release()
100
 	}
79
 	}
101
-	response.err = err
102
 
80
 
103
-	// always call Wait() to ensure resource cleanup
104
-	err = cmd.Wait()
81
+	inputBytes, err := json.Marshal(IPScriptInput{IP: addr.String()})
82
+	if err != nil {
83
+		return
84
+	}
85
+	outBytes, err := RunScript(config.Command, config.Args, inputBytes, config.Timeout, config.KillTimeout)
86
+	if err != nil {
87
+		return
88
+	}
89
+	err = json.Unmarshal(outBytes, &output)
105
 	if err != nil {
90
 	if err != nil {
106
-		response.err = err
91
+		return
107
 	}
92
 	}
108
 
93
 
109
-	channel <- response
94
+	if output.Error != "" {
95
+		err = fmt.Errorf("IP ban process reported error: %s", output.Error)
96
+	} else if !(IPAccepted <= output.Result && output.Result <= IPRequireSASL) {
97
+		err = fmt.Errorf("Invalid result from IP checking script: %d", output.Result)
98
+	}
99
+
100
+	return
110
 }
101
 }

+ 10
- 5
irc/client.go 查看文件

101
 	cloakedHostname    string
101
 	cloakedHostname    string
102
 	realname           string
102
 	realname           string
103
 	realIP             net.IP
103
 	realIP             net.IP
104
+	requireSASL        bool
104
 	registered         bool
105
 	registered         bool
105
 	registrationTimer  *time.Timer
106
 	registrationTimer  *time.Timer
106
 	resumeID           string
107
 	resumeID           string
297
 
298
 
298
 // RunClient sets up a new client and runs its goroutine.
299
 // RunClient sets up a new client and runs its goroutine.
299
 func (server *Server) RunClient(conn IRCConn) {
300
 func (server *Server) RunClient(conn IRCConn) {
301
+	config := server.Config()
300
 	wConn := conn.UnderlyingConn()
302
 	wConn := conn.UnderlyingConn()
301
-	var isBanned bool
303
+	var isBanned, requireSASL bool
302
 	var banMsg string
304
 	var banMsg string
303
 	realIP := utils.AddrToIP(wConn.RemoteAddr())
305
 	realIP := utils.AddrToIP(wConn.RemoteAddr())
304
 	var proxiedIP net.IP
306
 	var proxiedIP net.IP
313
 			proxiedIP = wConn.ProxiedIP
315
 			proxiedIP = wConn.ProxiedIP
314
 			ipToCheck = proxiedIP
316
 			ipToCheck = proxiedIP
315
 		}
317
 		}
316
-		isBanned, banMsg = server.checkBans(ipToCheck)
318
+		// XXX only run the check script now if the IP cannot be replaced by PROXY or WEBIRC,
319
+		// otherwise we'll do it in ApplyProxiedIP.
320
+		checkScripts := proxiedIP != nil || !utils.IPInNets(realIP, config.Server.proxyAllowedFromNets)
321
+		isBanned, requireSASL, banMsg = server.checkBans(config, ipToCheck, checkScripts)
317
 	}
322
 	}
318
 
323
 
319
 	if isBanned {
324
 	if isBanned {
327
 	server.logger.Info("connect-ip", fmt.Sprintf("Client connecting: real IP %v, proxied IP %v", realIP, proxiedIP))
332
 	server.logger.Info("connect-ip", fmt.Sprintf("Client connecting: real IP %v, proxied IP %v", realIP, proxiedIP))
328
 
333
 
329
 	now := time.Now().UTC()
334
 	now := time.Now().UTC()
330
-	config := server.Config()
331
 	// give them 1k of grace over the limit:
335
 	// give them 1k of grace over the limit:
332
 	socket := NewSocket(conn, config.Server.MaxSendQBytes)
336
 	socket := NewSocket(conn, config.Server.MaxSendQBytes)
333
 	client := &Client{
337
 	client := &Client{
347
 		nickMaskString: "*", // * is used until actual nick is given
351
 		nickMaskString: "*", // * is used until actual nick is given
348
 		realIP:         realIP,
352
 		realIP:         realIP,
349
 		proxiedIP:      proxiedIP,
353
 		proxiedIP:      proxiedIP,
354
+		requireSASL:    requireSASL,
350
 	}
355
 	}
351
 	client.writerSemaphore.Initialize(1)
356
 	client.writerSemaphore.Initialize(1)
352
 	client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
357
 	client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
554
 	authFailSaslRequired
559
 	authFailSaslRequired
555
 )
560
 )
556
 
561
 
557
-func (client *Client) isAuthorized(server *Server, config *Config, session *Session) AuthOutcome {
562
+func (client *Client) isAuthorized(server *Server, config *Config, session *Session, forceRequireSASL bool) AuthOutcome {
558
 	saslSent := client.account != ""
563
 	saslSent := client.account != ""
559
 	// PASS requirement
564
 	// PASS requirement
560
 	if (config.Server.passwordBytes != nil) && session.passStatus != serverPassSuccessful && !(config.Accounts.SkipServerPassword && saslSent) {
565
 	if (config.Server.passwordBytes != nil) && session.passStatus != serverPassSuccessful && !(config.Accounts.SkipServerPassword && saslSent) {
565
 		return authFailTorSaslRequired
570
 		return authFailTorSaslRequired
566
 	}
571
 	}
567
 	// finally, enforce require-sasl
572
 	// finally, enforce require-sasl
568
-	if !saslSent && (config.Accounts.RequireSasl.Enabled || server.Defcon() <= 2) &&
573
+	if !saslSent && (forceRequireSASL || config.Accounts.RequireSasl.Enabled || server.Defcon() <= 2) &&
569
 		!utils.IPInNets(session.IP(), config.Accounts.RequireSasl.exemptedNets) {
574
 		!utils.IPInNets(session.IP(), config.Accounts.RequireSasl.exemptedNets) {
570
 		return authFailSaslRequired
575
 		return authFailSaslRequired
571
 	}
576
 	}

+ 14
- 8
irc/config.go 查看文件

282
 	AuthScript  AuthScriptConfig `yaml:"auth-script"`
282
 	AuthScript  AuthScriptConfig `yaml:"auth-script"`
283
 }
283
 }
284
 
284
 
285
+type ScriptConfig struct {
286
+	Enabled        bool
287
+	Command        string
288
+	Args           []string
289
+	Timeout        time.Duration
290
+	KillTimeout    time.Duration `yaml:"kill-timeout"`
291
+	MaxConcurrency uint          `yaml:"max-concurrency"`
292
+}
293
+
285
 type AuthScriptConfig struct {
294
 type AuthScriptConfig struct {
286
-	Enabled     bool
287
-	Command     string
288
-	Args        []string
289
-	Autocreate  bool
290
-	Timeout     time.Duration
291
-	KillTimeout time.Duration `yaml:"kill-timeout"`
295
+	ScriptConfig `yaml:",inline"`
296
+	Autocreate   bool
292
 }
297
 }
293
 
298
 
294
 // AccountRegistrationConfig controls account registration.
299
 // AccountRegistrationConfig controls account registration.
526
 		supportedCaps *caps.Set
531
 		supportedCaps *caps.Set
527
 		capValues     caps.Values
532
 		capValues     caps.Values
528
 		Casemapping   Casemapping
533
 		Casemapping   Casemapping
529
-		EnforceUtf8   bool   `yaml:"enforce-utf8"`
530
-		OutputPath    string `yaml:"output-path"`
534
+		EnforceUtf8   bool         `yaml:"enforce-utf8"`
535
+		OutputPath    string       `yaml:"output-path"`
536
+		IPCheckScript ScriptConfig `yaml:"ip-check-script"`
531
 	}
537
 	}
532
 
538
 
533
 	Roleplay struct {
539
 	Roleplay struct {

+ 2
- 1
irc/gateways.go 查看文件

77
 	}
77
 	}
78
 	proxiedIP = proxiedIP.To16()
78
 	proxiedIP = proxiedIP.To16()
79
 
79
 
80
-	isBanned, banMsg := client.server.checkBans(proxiedIP)
80
+	isBanned, requireSASL, banMsg := client.server.checkBans(client.server.Config(), proxiedIP, true)
81
 	if isBanned {
81
 	if isBanned {
82
 		return errBanned, banMsg
82
 		return errBanned, banMsg
83
 	}
83
 	}
84
+	client.requireSASL = requireSASL
84
 	// successfully added a limiter entry for the proxied IP;
85
 	// successfully added a limiter entry for the proxied IP;
85
 	// remove the entry for the real IP if applicable (#197)
86
 	// remove the entry for the real IP if applicable (#197)
86
 	client.server.connectionLimiter.RemoveClient(session.realIP)
87
 	client.server.connectionLimiter.RemoveClient(session.realIP)

+ 83
- 0
irc/script.go 查看文件

1
+// Copyright (c) 2020 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"bufio"
8
+	"io"
9
+	"os/exec"
10
+	"syscall"
11
+	"time"
12
+)
13
+
14
+// general-purpose scripting API for oragono "plugins"
15
+// invoke a command, send it a single newline-terminated string of bytes (typically JSON)
16
+// get back another newline-terminated string of bytes (or an error)
17
+
18
+// internal tupling of output and error for passing over a channel
19
+type scriptResponse struct {
20
+	output []byte
21
+	err    error
22
+}
23
+
24
+func RunScript(command string, args []string, input []byte, timeout, killTimeout time.Duration) (output []byte, err error) {
25
+	cmd := exec.Command(command, args...)
26
+	stdin, err := cmd.StdinPipe()
27
+	if err != nil {
28
+		return
29
+	}
30
+	stdout, err := cmd.StdoutPipe()
31
+	if err != nil {
32
+		return
33
+	}
34
+
35
+	channel := make(chan scriptResponse, 1)
36
+	err = cmd.Start()
37
+	if err != nil {
38
+		return
39
+	}
40
+	stdin.Write(input)
41
+	stdin.Write([]byte{'\n'})
42
+
43
+	// lots of potential race conditions here. we want to ensure that Wait()
44
+	// will be called, and will return, on the other goroutine, no matter
45
+	// where it is blocked. If it's blocked on ReadBytes(), we will kill it
46
+	// (first with SIGTERM, then with SIGKILL) and ReadBytes will return
47
+	// with EOF. If it's blocked on Wait(), then one of the kill signals
48
+	// will succeed and unblock it.
49
+	go processScriptOutput(cmd, stdout, channel)
50
+	outputTimer := time.NewTimer(timeout)
51
+	select {
52
+	case response := <-channel:
53
+		return response.output, response.err
54
+	case <-outputTimer.C:
55
+	}
56
+
57
+	err = errTimedOut
58
+	cmd.Process.Signal(syscall.SIGTERM)
59
+	termTimer := time.NewTimer(killTimeout)
60
+	select {
61
+	case <-channel:
62
+		return
63
+	case <-termTimer.C:
64
+	}
65
+
66
+	cmd.Process.Kill()
67
+	return
68
+}
69
+
70
+func processScriptOutput(cmd *exec.Cmd, stdout io.Reader, channel chan scriptResponse) {
71
+	var response scriptResponse
72
+
73
+	reader := bufio.NewReader(stdout)
74
+	response.output, response.err = reader.ReadBytes('\n')
75
+
76
+	// always call Wait() to ensure resource cleanup
77
+	err := cmd.Wait()
78
+	if err != nil {
79
+		response.err = err
80
+	}
81
+
82
+	channel <- response
83
+}

+ 2
- 0
irc/semaphores.go 查看文件

27
 	// each distinct operation MUST have its own semaphore;
27
 	// each distinct operation MUST have its own semaphore;
28
 	// methods that acquire a semaphore MUST NOT call methods that acquire another
28
 	// methods that acquire a semaphore MUST NOT call methods that acquire another
29
 	ClientDestroy utils.Semaphore
29
 	ClientDestroy utils.Semaphore
30
+	IPCheckScript utils.Semaphore
31
+	AuthScript    utils.Semaphore
30
 }
32
 }
31
 
33
 
32
 // Initialize initializes a set of server semaphores.
34
 // Initialize initializes a set of server semaphores.

+ 64
- 22
irc/server.go 查看文件

150
 	}
150
 	}
151
 }
151
 }
152
 
152
 
153
-func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
153
+func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool) (banned bool, requireSASL bool, message string) {
154
 	if server.Defcon() == 1 {
154
 	if server.Defcon() == 1 {
155
 		if !(ipaddr.IsLoopback() || utils.IPInNets(ipaddr, server.Config().Server.secureNets)) {
155
 		if !(ipaddr.IsLoopback() || utils.IPInNets(ipaddr, server.Config().Server.secureNets)) {
156
-			return true, "New connections to this server are temporarily restricted"
156
+			return true, false, "New connections to this server are temporarily restricted"
157
 		}
157
 		}
158
 	}
158
 	}
159
 
159
 
161
 	isBanned, info := server.dlines.CheckIP(ipaddr)
161
 	isBanned, info := server.dlines.CheckIP(ipaddr)
162
 	if isBanned {
162
 	if isBanned {
163
 		server.logger.Info("connect-ip", fmt.Sprintf("Client from %v rejected by d-line", ipaddr))
163
 		server.logger.Info("connect-ip", fmt.Sprintf("Client from %v rejected by d-line", ipaddr))
164
-		return true, info.BanMessage("You are banned from this server (%s)")
164
+		return true, false, info.BanMessage("You are banned from this server (%s)")
165
 	}
165
 	}
166
 
166
 
167
 	// check connection limits
167
 	// check connection limits
169
 	if err == connection_limits.ErrLimitExceeded {
169
 	if err == connection_limits.ErrLimitExceeded {
170
 		// too many connections from one client, tell the client and close the connection
170
 		// too many connections from one client, tell the client and close the connection
171
 		server.logger.Info("connect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr))
171
 		server.logger.Info("connect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr))
172
-		return true, "Too many clients from your network"
172
+		return true, false, "Too many clients from your network"
173
 	} else if err == connection_limits.ErrThrottleExceeded {
173
 	} else if err == connection_limits.ErrThrottleExceeded {
174
-		duration := server.Config().Server.IPLimits.BanDuration
175
-		if duration == 0 {
176
-			return false, ""
174
+		duration := config.Server.IPLimits.BanDuration
175
+		if duration != 0 {
176
+			server.dlines.AddIP(ipaddr, duration, throttleMessage,
177
+				"Exceeded automated connection throttle", "auto.connection.throttler")
178
+			// they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now,
179
+			// and once their temporary DLINE is finished they can fill up the throttler again
180
+			server.connectionLimiter.ResetThrottle(ipaddr)
177
 		}
181
 		}
178
-		server.dlines.AddIP(ipaddr, duration, throttleMessage, "Exceeded automated connection throttle", "auto.connection.throttler")
179
-		// they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now,
180
-		// and once their temporary DLINE is finished they can fill up the throttler again
181
-		server.connectionLimiter.ResetThrottle(ipaddr)
182
-
183
-		// this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us
184
 		server.logger.Info(
182
 		server.logger.Info(
185
 			"connect-ip",
183
 			"connect-ip",
186
 			fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration))
184
 			fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration))
187
-		return true, throttleMessage
185
+		return true, false, throttleMessage
188
 	} else if err != nil {
186
 	} else if err != nil {
189
 		server.logger.Warning("internal", "unexpected ban result", err.Error())
187
 		server.logger.Warning("internal", "unexpected ban result", err.Error())
190
 	}
188
 	}
191
 
189
 
192
-	return false, ""
190
+	if checkScripts && config.Server.IPCheckScript.Enabled {
191
+		output, err := CheckIPBan(server.semaphores.IPCheckScript, config.Server.IPCheckScript, ipaddr)
192
+		if err != nil {
193
+			server.logger.Error("internal", "couldn't check IP ban script", ipaddr.String(), err.Error())
194
+			return false, false, ""
195
+		}
196
+		// TODO: currently no way to cache results other than IPBanned
197
+		if output.Result == IPBanned && output.CacheSeconds != 0 {
198
+			network, err := utils.NormalizedNetFromString(output.CacheNet)
199
+			if err != nil {
200
+				server.logger.Error("internal", "invalid dline net from IP ban script", ipaddr.String(), output.CacheNet)
201
+			} else {
202
+				dlineDuration := time.Duration(output.CacheSeconds) * time.Second
203
+				err := server.dlines.AddNetwork(network, dlineDuration, output.BanMessage, "", "")
204
+				if err != nil {
205
+					server.logger.Error("internal", "couldn't set dline from IP ban script", ipaddr.String(), err.Error())
206
+				}
207
+			}
208
+		}
209
+		if output.Result == IPBanned {
210
+			// XXX roll back IP connection/throttling addition for the IP
211
+			server.connectionLimiter.RemoveClient(ipaddr)
212
+			server.logger.Info("connect-ip", "Rejected client due to ip-check-script", ipaddr.String())
213
+			return true, false, output.BanMessage
214
+		} else if output.Result == IPRequireSASL {
215
+			server.logger.Info("connect-ip", "Requiring SASL from client due to ip-check-script", ipaddr.String())
216
+			return false, true, ""
217
+		}
218
+	}
219
+
220
+	return false, false, ""
193
 }
221
 }
194
 
222
 
195
 func (server *Server) checkTorLimits() (banned bool, message string) {
223
 func (server *Server) checkTorLimits() (banned bool, message string) {
214
 		return // whether we succeeded or failed, either way `c` is not getting registered
242
 		return // whether we succeeded or failed, either way `c` is not getting registered
215
 	}
243
 	}
216
 
244
 
245
+	// XXX PROXY or WEBIRC MUST be sent as the first line of the session;
246
+	// if we are here at all that means we have the final value of the IP
247
+	if session.rawHostname == "" {
248
+		session.client.lookupHostname(session, false)
249
+	}
250
+
217
 	// try to complete registration normally
251
 	// try to complete registration normally
218
 	// XXX(#1057) username can be filled in by an ident query without the client
252
 	// XXX(#1057) username can be filled in by an ident query without the client
219
 	// having sent USER: check for both username and realname to ensure they did
253
 	// having sent USER: check for both username and realname to ensure they did
229
 	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
263
 	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
230
 	// before completing the other registration commands
264
 	// before completing the other registration commands
231
 	config := server.Config()
265
 	config := server.Config()
232
-	authOutcome := c.isAuthorized(server, config, session)
266
+	authOutcome := c.isAuthorized(server, config, session, c.requireSASL)
233
 	var quitMessage string
267
 	var quitMessage string
234
 	switch authOutcome {
268
 	switch authOutcome {
235
 	case authFailPass:
269
 	case authFailPass:
244
 		return true
278
 		return true
245
 	}
279
 	}
246
 
280
 
247
-	// we have the final value of the IP address: do the hostname lookup
248
-	// (nickmask will be set below once nickname assignment succeeds)
249
-	if session.rawHostname == "" {
250
-		session.client.lookupHostname(session, false)
251
-	}
252
-
253
 	rb := NewResponseBuffer(session)
281
 	rb := NewResponseBuffer(session)
254
 	nickError := performNickChange(server, c, c, session, c.preregNick, rb)
282
 	nickError := performNickChange(server, c, c, session, c.preregNick, rb)
255
 	rb.Send(true)
283
 	rb.Send(true)
489
 			return fmt.Errorf("Cannot enable or disable relaying after launching the server, rehash aborted")
517
 			return fmt.Errorf("Cannot enable or disable relaying after launching the server, rehash aborted")
490
 		} else if oldConfig.Server.Relaymsg.Separators != config.Server.Relaymsg.Separators {
518
 		} else if oldConfig.Server.Relaymsg.Separators != config.Server.Relaymsg.Separators {
491
 			return fmt.Errorf("Cannot change relaying separators after launching the server, rehash aborted")
519
 			return fmt.Errorf("Cannot change relaying separators after launching the server, rehash aborted")
520
+		} else if oldConfig.Server.IPCheckScript.MaxConcurrency != config.Server.IPCheckScript.MaxConcurrency ||
521
+			oldConfig.Accounts.AuthScript.MaxConcurrency != config.Accounts.AuthScript.MaxConcurrency {
522
+			return fmt.Errorf("Cannot change max-concurrency for scripts after launching the server, rehash aborted")
492
 		}
523
 		}
493
 	}
524
 	}
494
 
525
 
513
 	server.logger.Debug("server", "Regenerating HELP indexes for new languages")
544
 	server.logger.Debug("server", "Regenerating HELP indexes for new languages")
514
 	server.helpIndexManager.GenerateIndices(config.languageManager)
545
 	server.helpIndexManager.GenerateIndices(config.languageManager)
515
 
546
 
547
+	if initial {
548
+		maxIPConc := int(config.Server.IPCheckScript.MaxConcurrency)
549
+		if maxIPConc != 0 {
550
+			server.semaphores.IPCheckScript.Initialize(maxIPConc)
551
+		}
552
+		maxAuthConc := int(config.Accounts.AuthScript.MaxConcurrency)
553
+		if maxAuthConc != 0 {
554
+			server.semaphores.AuthScript.Initialize(maxAuthConc)
555
+		}
556
+	}
557
+
516
 	if oldConfig != nil {
558
 	if oldConfig != nil {
517
 		// if certain features were enabled by rehash, we need to load the corresponding data
559
 		// if certain features were enabled by rehash, we need to load the corresponding data
518
 		// from the store
560
 		// from the store

Loading…
取消
儲存