Browse Source

scripting API for IP bans

See discussion on #68.
tags/v2.4.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
1a98a37a75
10 changed files with 267 additions and 101 deletions
  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 View File

@@ -243,6 +243,22 @@ server:
243 243
             #     max-concurrent-connections: 128
244 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 262
     # IP cloaking hides users' IP addresses from other users and from channel admins
247 263
     # (but not from server admins), while still allowing channel admins to ban
248 264
     # offending IP addresses or networks. In place of hostnames derived from reverse
@@ -483,6 +499,8 @@ accounts:
483 499
         timeout: 9s
484 500
         # how long after the SIGTERM before we follow up with a SIGKILL:
485 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 505
 # channel options
488 506
 channels:

+ 18
- 0
default.yaml View File

@@ -270,6 +270,22 @@ server:
270 270
             #     max-concurrent-connections: 128
271 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 289
     # IP cloaking hides users' IP addresses from other users and from channel admins
274 290
     # (but not from server admins), while still allowing channel admins to ban
275 291
     # offending IP addresses or networks. In place of hostnames derived from reverse
@@ -511,6 +527,8 @@ accounts:
511 527
         timeout: 9s
512 528
         # how long after the SIGTERM before we follow up with a SIGKILL:
513 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 533
 # channel options
516 534
 channels:

+ 2
- 2
irc/accounts.go View File

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

+ 54
- 63
irc/authscript.go View File

@@ -4,13 +4,11 @@
4 4
 package irc
5 5
 
6 6
 import (
7
-	"bufio"
8 7
 	"encoding/json"
9 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 14
 // JSON-serializable input and output types for the script
@@ -27,84 +25,77 @@ type AuthScriptOutput struct {
27 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 34
 	inputBytes, err := json.Marshal(input)
38 35
 	if err != nil {
39 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 39
 	if err != nil {
44 40
 		return
45 41
 	}
46
-	stdout, err := cmd.StdoutPipe()
42
+	err = json.Unmarshal(outBytes, &output)
47 43
 	if err != nil {
48 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 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 View File

@@ -101,6 +101,7 @@ type Client struct {
101 101
 	cloakedHostname    string
102 102
 	realname           string
103 103
 	realIP             net.IP
104
+	requireSASL        bool
104 105
 	registered         bool
105 106
 	registrationTimer  *time.Timer
106 107
 	resumeID           string
@@ -297,8 +298,9 @@ type ClientDetails struct {
297 298
 
298 299
 // RunClient sets up a new client and runs its goroutine.
299 300
 func (server *Server) RunClient(conn IRCConn) {
301
+	config := server.Config()
300 302
 	wConn := conn.UnderlyingConn()
301
-	var isBanned bool
303
+	var isBanned, requireSASL bool
302 304
 	var banMsg string
303 305
 	realIP := utils.AddrToIP(wConn.RemoteAddr())
304 306
 	var proxiedIP net.IP
@@ -313,7 +315,10 @@ func (server *Server) RunClient(conn IRCConn) {
313 315
 			proxiedIP = wConn.ProxiedIP
314 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 324
 	if isBanned {
@@ -327,7 +332,6 @@ func (server *Server) RunClient(conn IRCConn) {
327 332
 	server.logger.Info("connect-ip", fmt.Sprintf("Client connecting: real IP %v, proxied IP %v", realIP, proxiedIP))
328 333
 
329 334
 	now := time.Now().UTC()
330
-	config := server.Config()
331 335
 	// give them 1k of grace over the limit:
332 336
 	socket := NewSocket(conn, config.Server.MaxSendQBytes)
333 337
 	client := &Client{
@@ -347,6 +351,7 @@ func (server *Server) RunClient(conn IRCConn) {
347 351
 		nickMaskString: "*", // * is used until actual nick is given
348 352
 		realIP:         realIP,
349 353
 		proxiedIP:      proxiedIP,
354
+		requireSASL:    requireSASL,
350 355
 	}
351 356
 	client.writerSemaphore.Initialize(1)
352 357
 	client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
@@ -554,7 +559,7 @@ const (
554 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 563
 	saslSent := client.account != ""
559 564
 	// PASS requirement
560 565
 	if (config.Server.passwordBytes != nil) && session.passStatus != serverPassSuccessful && !(config.Accounts.SkipServerPassword && saslSent) {
@@ -565,7 +570,7 @@ func (client *Client) isAuthorized(server *Server, config *Config, session *Sess
565 570
 		return authFailTorSaslRequired
566 571
 	}
567 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 574
 		!utils.IPInNets(session.IP(), config.Accounts.RequireSasl.exemptedNets) {
570 575
 		return authFailSaslRequired
571 576
 	}

+ 14
- 8
irc/config.go View File

@@ -282,13 +282,18 @@ type AccountConfig struct {
282 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 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 299
 // AccountRegistrationConfig controls account registration.
@@ -526,8 +531,9 @@ type Config struct {
526 531
 		supportedCaps *caps.Set
527 532
 		capValues     caps.Values
528 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 539
 	Roleplay struct {

+ 2
- 1
irc/gateways.go View File

@@ -77,10 +77,11 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP net.IP, tls boo
77 77
 	}
78 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 81
 	if isBanned {
82 82
 		return errBanned, banMsg
83 83
 	}
84
+	client.requireSASL = requireSASL
84 85
 	// successfully added a limiter entry for the proxied IP;
85 86
 	// remove the entry for the real IP if applicable (#197)
86 87
 	client.server.connectionLimiter.RemoveClient(session.realIP)

+ 83
- 0
irc/script.go View File

@@ -0,0 +1,83 @@
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 View File

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

+ 64
- 22
irc/server.go View File

@@ -150,10 +150,10 @@ func (server *Server) Run() {
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 154
 	if server.Defcon() == 1 {
155 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,7 +161,7 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
161 161
 	isBanned, info := server.dlines.CheckIP(ipaddr)
162 162
 	if isBanned {
163 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 167
 	// check connection limits
@@ -169,27 +169,55 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
169 169
 	if err == connection_limits.ErrLimitExceeded {
170 170
 		// too many connections from one client, tell the client and close the connection
171 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 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 182
 		server.logger.Info(
185 183
 			"connect-ip",
186 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 186
 	} else if err != nil {
189 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 223
 func (server *Server) checkTorLimits() (banned bool, message string) {
@@ -214,6 +242,12 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
214 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 251
 	// try to complete registration normally
218 252
 	// XXX(#1057) username can be filled in by an ident query without the client
219 253
 	// having sent USER: check for both username and realname to ensure they did
@@ -229,7 +263,7 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
229 263
 	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
230 264
 	// before completing the other registration commands
231 265
 	config := server.Config()
232
-	authOutcome := c.isAuthorized(server, config, session)
266
+	authOutcome := c.isAuthorized(server, config, session, c.requireSASL)
233 267
 	var quitMessage string
234 268
 	switch authOutcome {
235 269
 	case authFailPass:
@@ -244,12 +278,6 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
244 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 281
 	rb := NewResponseBuffer(session)
254 282
 	nickError := performNickChange(server, c, c, session, c.preregNick, rb)
255 283
 	rb.Send(true)
@@ -489,6 +517,9 @@ func (server *Server) applyConfig(config *Config) (err error) {
489 517
 			return fmt.Errorf("Cannot enable or disable relaying after launching the server, rehash aborted")
490 518
 		} else if oldConfig.Server.Relaymsg.Separators != config.Server.Relaymsg.Separators {
491 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,6 +544,17 @@ func (server *Server) applyConfig(config *Config) (err error) {
513 544
 	server.logger.Debug("server", "Regenerating HELP indexes for new languages")
514 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 558
 	if oldConfig != nil {
517 559
 		// if certain features were enabled by rehash, we need to load the corresponding data
518 560
 		// from the store

Loading…
Cancel
Save