diff --git a/irc/connection_limits/limiter.go b/irc/connection_limits/limiter.go index a3fcec90..9b844d99 100644 --- a/irc/connection_limits/limiter.go +++ b/irc/connection_limits/limiter.go @@ -28,6 +28,7 @@ type CustomLimitConfig struct { // tuples the key-value pair of a CIDR and its custom limit/throttle values type customLimit struct { name [16]byte + customID string // operator-configured identifier for a custom net maxConcurrent int maxPerWindow int nets []flatip.IPNet @@ -103,6 +104,7 @@ func (config *LimiterConfig) postprocess() (err error) { maxConcurrent: customLimitConf.MaxConcurrent, maxPerWindow: customLimitConf.MaxPerWindow, name: md5.Sum([]byte(identifier)), + customID: identifier, nets: nets, }) } @@ -124,11 +126,11 @@ type Limiter struct { // addrToKey canonicalizes `addr` to a string key, and returns // the relevant connection limit and throttle max-per-window values -func (cl *Limiter) addrToKey(addr flatip.IP) (key limiterKey, limit int, throttle int) { +func (cl *Limiter) addrToKey(addr flatip.IP) (key limiterKey, customID string, limit int, throttle int) { for _, custom := range cl.config.customLimits { for _, net := range custom.nets { if net.Contains(addr) { - return limiterKey{maskedIP: custom.name, prefixLen: 0}, custom.maxConcurrent, custom.maxPerWindow + return limiterKey{maskedIP: custom.name, prefixLen: 0}, custom.customID, custom.maxConcurrent, custom.maxPerWindow } } } @@ -143,7 +145,7 @@ func (cl *Limiter) addrToKey(addr flatip.IP) (key limiterKey, limit int, throttl addr = addr.Mask(prefixLen, 128) } - return limiterKey{maskedIP: addr, prefixLen: uint8(prefixLen)}, cl.config.MaxConcurrent, cl.config.MaxPerWindow + return limiterKey{maskedIP: addr, prefixLen: uint8(prefixLen)}, "", cl.config.MaxConcurrent, cl.config.MaxPerWindow } // AddClient adds a client to our population if possible. If we can't, throws an error instead. @@ -156,7 +158,7 @@ func (cl *Limiter) AddClient(addr flatip.IP) error { return nil } - addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr) + addrString, _, maxConcurrent, maxPerWindow := cl.addrToKey(addr) // check limiter var count int @@ -200,7 +202,7 @@ func (cl *Limiter) RemoveClient(addr flatip.IP) { return } - addrString, _, _ := cl.addrToKey(addr) + addrString, _, _, _ := cl.addrToKey(addr) count := cl.limiter[addrString] count -= 1 if count < 0 { @@ -220,7 +222,7 @@ type LimiterStatus struct { ThrottleDuration time.Duration } -func (cl *Limiter) Status(addr flatip.IP) (status LimiterStatus) { +func (cl *Limiter) Status(addr flatip.IP) (netName string, status LimiterStatus) { cl.Lock() defer cl.Unlock() @@ -231,12 +233,20 @@ func (cl *Limiter) Status(addr flatip.IP) (status LimiterStatus) { status.ThrottleDuration = cl.config.Window - addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr) + limiterKey, customID, maxConcurrent, maxPerWindow := cl.addrToKey(addr) status.MaxCount = maxConcurrent status.MaxPerWindow = maxPerWindow - status.Count = cl.limiter[addrString] - status.Throttle = cl.throttler[addrString].Count + status.Count = cl.limiter[limiterKey] + status.Throttle = cl.throttler[limiterKey].Count + + netName = customID + if netName == "" { + netName = flatip.IPNet{ + IP: limiterKey.maskedIP, + PrefixLen: limiterKey.prefixLen, + }.String() + } return } @@ -250,7 +260,7 @@ func (cl *Limiter) ResetThrottle(addr flatip.IP) { return } - addrString, _, _ := cl.addrToKey(addr) + addrString, _, _, _ := cl.addrToKey(addr) delete(cl.throttler, addrString) } diff --git a/irc/connection_limits/limiter_test.go b/irc/connection_limits/limiter_test.go index 3bc0b39e..5328d2c0 100644 --- a/irc/connection_limits/limiter_test.go +++ b/irc/connection_limits/limiter_test.go @@ -50,20 +50,20 @@ func TestKeying(t *testing.T) { limiter.ApplyConfig(&config) // an ipv4 /32 looks like a /128 to us after applying the 4-in-6 mapping - key, maxConc, maxWin := limiter.addrToKey(easyParseIP("1.1.1.1")) + key, _, maxConc, maxWin := limiter.addrToKey(easyParseIP("1.1.1.1")) assertEqual(key.prefixLen, uint8(128), t) assertEqual(key.maskedIP[12:], []byte{1, 1, 1, 1}, t) assertEqual(maxConc, 4, t) assertEqual(maxWin, 8, t) testIPv6 := easyParseIP("2607:5301:201:3100::7426") - key, maxConc, maxWin = limiter.addrToKey(testIPv6) + key, _, maxConc, maxWin = limiter.addrToKey(testIPv6) assertEqual(key.prefixLen, uint8(64), t) assertEqual(flatip.IP(key.maskedIP), easyParseIP("2607:5301:201:3100::"), t) assertEqual(maxConc, 4, t) assertEqual(maxWin, 8, t) - key, maxConc, maxWin = limiter.addrToKey(easyParseIP("8.8.4.4")) + key, _, maxConc, maxWin = limiter.addrToKey(easyParseIP("8.8.4.4")) assertEqual(key.prefixLen, uint8(0), t) assertEqual([16]byte(key.maskedIP), md5.Sum([]byte("google")), t) assertEqual(maxConc, 128, t) diff --git a/irc/help.go b/irc/help.go index f7537463..432b8100 100644 --- a/irc/help.go +++ b/irc/help.go @@ -107,14 +107,6 @@ For instance, this would set the kill, oper, account and xline snomasks on dan: // Help contains the help strings distributed with the IRCd. var Help = map[string]HelpEntry{ // Commands - "acc": { - text: `ACC LS -ACC REGISTER [callback_namespace:] [cred_type] : -ACC VERIFY - -Used in account registration. See the relevant specs for more info: -https://oragono.io/specs.html`, - }, "ambiance": { text: `AMBIANCE diff --git a/irc/uban.go b/irc/uban.go index 94aa174c..d631dda1 100644 --- a/irc/uban.go +++ b/irc/uban.go @@ -303,13 +303,12 @@ func ubanInfoHandler(client *Client, target ubanTarget, params []string, rb *Res func ubanInfoCIDR(client *Client, target ubanTarget, rb *ResponseBuffer) { if target.cidr.PrefixLen == 128 { - status := client.server.connectionLimiter.Status(target.cidr.IP) - str := target.cidr.IP.String() + netName, status := client.server.connectionLimiter.Status(target.cidr.IP) if status.Exempt { - rb.Notice(fmt.Sprintf(client.t("IP %s is exempt from connection limits"), str)) + rb.Notice(fmt.Sprintf(client.t("IP %s is exempt from connection limits"), target.cidr.IP.String())) } else { - rb.Notice(fmt.Sprintf(client.t("IP %[1]s has %[2]d active connections out of a maximum of %[3]d"), str, status.Count, status.MaxCount)) - rb.Notice(fmt.Sprintf(client.t("IP %[1]s has had %[2]d connection attempts in the past %[3]v, out of a maximum of %[4]d"), str, status.Throttle, status.ThrottleDuration, status.MaxPerWindow)) + rb.Notice(fmt.Sprintf(client.t("Network %[1]s has %[2]d active connections out of a maximum of %[3]d"), netName, status.Count, status.MaxCount)) + rb.Notice(fmt.Sprintf(client.t("Network %[1]s has had %[2]d connection attempts in the past %[3]v, out of a maximum of %[4]d"), netName, status.Throttle, status.ThrottleDuration, status.MaxPerWindow)) } } @@ -364,15 +363,22 @@ func ubanInfoNick(client *Client, target ubanTarget, rb *ResponseBuffer) { mcl := client.server.clients.Get(target.nickOrMask) if mcl != nil { details := mcl.Details() + sessions := mcl.Sessions() + ip := mcl.IP() + sendIPBanWarning := false if details.account == "" { - rb.Notice(fmt.Sprintf(client.t("Client %[1]s is unauthenticated and connected from %[2]s"), details.nick, mcl.IP().String())) + rb.Notice(fmt.Sprintf(client.t("Client %[1]s is unauthenticated and connected from %[2]s"), details.nick, ip.String())) + sendIPBanWarning = true } else { rb.Notice(fmt.Sprintf(client.t("Client %[1]s is logged into account %[2]s and has %[3]d active clients (see /NICKSERV CLIENTS LIST %[4]s for more info"), details.nick, details.accountName, len(mcl.Sessions()), details.nick)) - ip := mcl.IP() - if !ip.IsLoopback() { - rb.Notice(fmt.Sprintf(client.t("Client %[1]s is associated with IP %[2]s; you can ban this IP with /UBAN ADD"), details.nick, ip.String())) + if !ip.IsLoopback() && len(sessions) == 1 { + rb.Notice(fmt.Sprintf(client.t("Client %[1]s is associated with IP %[2]s"), details.nick, ip.String())) + sendIPBanWarning = true } } + if sendIPBanWarning { + rb.Notice(client.t("Warning: banning this IP or a network that contains it may affect other users. Use /UBAN INFO on the candidate IP or network for more information.")) + } } else { rb.Notice(fmt.Sprintf(client.t("No client is currently using that nickname"))) }