diff --git a/irc/accounts.go b/irc/accounts.go index 2498ffa2..488af3fc 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -16,6 +16,7 @@ import ( "unicode" "github.com/ergochat/irc-go/ircutils" + "github.com/tidwall/buntdb" "github.com/xdg-go/scram" "github.com/ergochat/ergo/irc/connection_limits" @@ -24,7 +25,6 @@ import ( "github.com/ergochat/ergo/irc/modes" "github.com/ergochat/ergo/irc/passwd" "github.com/ergochat/ergo/irc/utils" - "github.com/tidwall/buntdb" ) const ( @@ -474,6 +474,8 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames am.Unregister(casefoldedAccount, true) return ®istrationCallbackError{underlying: err} } else { + am.server.logger.Info("accounts", + fmt.Sprintf("nickname %s registered account %s, pending verification", client.Nick(), account)) return am.server.store.Update(func(tx *buntdb.Tx) error { _, _, err = tx.Set(verificationCodeKey, code, setOptions) return err diff --git a/irc/chanserv.go b/irc/chanserv.go index 81595f57..d50c19f9 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -681,6 +681,7 @@ func csPurgeAddHandler(service *ircService, client *Client, params []string, ope } } service.Notice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname)) + client.server.snomasks.Send(sno.LocalChannels, fmt.Sprintf("Operator %s purged channel %s [reason: %s]", operName, chname, reason)) case errInvalidChannelName: service.Notice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname)) default: @@ -698,6 +699,7 @@ func csPurgeDelHandler(service *ircService, client *Client, params []string, ope switch client.server.channels.Unpurge(chname) { case nil: service.Notice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname)) + client.server.snomasks.Send(sno.LocalChannels, fmt.Sprintf("Operator %s removed purge of channel %s", operName, chname)) case errNoSuchChannel: service.Notice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname)) default: diff --git a/irc/flatip/flatip.go b/irc/flatip/flatip.go index 7c603350..0084cf2b 100644 --- a/irc/flatip/flatip.go +++ b/irc/flatip/flatip.go @@ -155,6 +155,14 @@ func (cidr IPNet) Contains(ip IP) bool { return cidr.IP == maskedIP } +func (cidr IPNet) Size() (ones, bits int) { + if cidr.IP.IsIPv4() { + return int(cidr.PrefixLen) - 96, 32 + } else { + return int(cidr.PrefixLen), 128 + } +} + // FromNetIPnet converts a net.IPNet into an IPNet. func FromNetIPNet(network net.IPNet) (result IPNet) { ones, _ := network.Mask.Size() diff --git a/irc/flatip/flatip_test.go b/irc/flatip/flatip_test.go index c2aae9a8..f689d6dd 100644 --- a/irc/flatip/flatip_test.go +++ b/irc/flatip/flatip_test.go @@ -2,8 +2,10 @@ package flatip import ( "bytes" + "fmt" "math/rand" "net" + "reflect" "testing" "time" ) @@ -86,6 +88,38 @@ func doMaskingTest(ip net.IP, t *testing.T) { } } +func assertEqual(found, expected interface{}) { + if !reflect.DeepEqual(found, expected) { + panic(fmt.Sprintf("expected %#v, found %#v", expected, found)) + } +} + +func TestSize(t *testing.T) { + _, net, err := ParseCIDR("8.8.8.8/24") + if err != nil { + panic(err) + } + ones, bits := net.Size() + assertEqual(ones, 24) + assertEqual(bits, 32) + + _, net, err = ParseCIDR("2001::0db8/64") + if err != nil { + panic(err) + } + ones, bits = net.Size() + assertEqual(ones, 64) + assertEqual(bits, 128) + + _, net, err = ParseCIDR("2001::0db8/96") + if err != nil { + panic(err) + } + ones, bits = net.Size() + assertEqual(ones, 96) + assertEqual(bits, 128) +} + func TestMasking(t *testing.T) { for _, ipstr := range testIPStrs { doMaskingTest(easyParseIP(ipstr), t) diff --git a/irc/handlers.go b/irc/handlers.go index 820add90..0fb4436c 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -81,6 +81,10 @@ func registrationErrorToMessage(config *Config, client *Client, err error) (mess return } +func announcePendingReg(client *Client, rb *ResponseBuffer, accountName string) { + client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] attempted to register account $c[grey][$r%s$c[grey]] from IP %s, pending verification"), client.Nick(), accountName, rb.session.IP().String())) +} + // helper function to dispatch messages when a client successfully registers func sendSuccessfulRegResponse(service *ircService, client *Client, rb *ResponseBuffer) { details := client.Details() @@ -2632,6 +2636,7 @@ func registerHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res } else { rb.Add(nil, server.name, "REGISTER", "VERIFICATION_REQUIRED", accountName, fmt.Sprintf(client.t("Account created, pending verification; verification code has been sent to %s"), callbackValue)) client.registerCmdSent = true + announcePendingReg(client, rb, accountName) } case errAccountAlreadyRegistered, errAccountAlreadyUnregistered, errAccountMustHoldNick: rb.Add(nil, server.name, "FAIL", "REGISTER", "USERNAME_EXISTS", accountName, client.t("Username is already registered or otherwise unavailable")) diff --git a/irc/hostserv.go b/irc/hostserv.go index 86ddf760..fedfbbea 100644 --- a/irc/hostserv.go +++ b/irc/hostserv.go @@ -10,6 +10,7 @@ import ( "github.com/ergochat/irc-go/ircfmt" + "github.com/ergochat/ergo/irc/sno" "github.com/ergochat/ergo/irc/utils" ) @@ -159,6 +160,7 @@ func validateVhost(server *Server, vhost string, oper bool) error { } func hsSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + oper := client.Oper() user := params[0] var vhost string @@ -176,8 +178,10 @@ func hsSetHandler(service *ircService, server *Server, client *Client, command s service.Notice(rb, client.t("An error occurred")) } else if vhost != "" { service.Notice(rb, client.t("Successfully set vhost")) + server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Operator %s set vhost %s on account %s", oper.Name, user, vhost)) } else { service.Notice(rb, client.t("Successfully cleared vhost")) + server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Operator %s cleared vhost on account %s", oper.Name, user)) } } diff --git a/irc/nickserv.go b/irc/nickserv.go index c9b4fda1..f450f3fd 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -1004,6 +1004,7 @@ func nsRegisterHandler(service *ircService, server *Server, client *Client, comm messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s") message := fmt.Sprintf(messageTemplate, callbackValue) service.Notice(rb, message) + announcePendingReg(client, rb, account) } } else { // details could not be stored and relevant numerics have been dispatched, abort diff --git a/irc/uban.go b/irc/uban.go index 424d1b7a..8f0e646a 100644 --- a/irc/uban.go +++ b/irc/uban.go @@ -366,7 +366,14 @@ func ubanInfoHandler(client *Client, target ubanTarget, params []string, rb *Res } func ubanInfoCIDR(client *Client, target ubanTarget, rb *ResponseBuffer) { - if target.cidr.PrefixLen == 128 { + config := client.server.Config() + // show connection limiter/throttler state if this CIDR is entirely + // contained in a single limiter/throttler bucket: + ones, bits := target.cidr.Size() + showLimiter := (bits == 32 && ones >= config.Server.IPLimits.CidrLenIPv4) || + (bits == 128 && ones >= config.Server.IPLimits.CidrLenIPv6) + sendMaskWarning := (bits == 128 && ones > config.Server.IPLimits.CidrLenIPv6) + if showLimiter { 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"), target.cidr.IP.String())) @@ -391,6 +398,10 @@ func ubanInfoCIDR(client *Client, target ubanTarget, rb *ResponseBuffer) { rb.Notice(line) } } + if sendMaskWarning { + rb.Notice(fmt.Sprintf(client.t("Note: try evaluating a wider IPv6 CIDR like %s/%d"), + target.cidr.IP.String(), config.Server.IPLimits.CidrLenIPv6)) + } } func ubanInfoNickmask(client *Client, target ubanTarget, rb *ResponseBuffer) { @@ -434,7 +445,7 @@ func ubanInfoNick(client *Client, target ubanTarget, rb *ResponseBuffer) { 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)) + 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)) 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