diff --git a/irc/dline.go b/irc/dline.go index 419ad612..db7d4ffb 100644 --- a/irc/dline.go +++ b/irc/dline.go @@ -20,6 +20,8 @@ const ( // IPBanInfo holds info about an IP/net ban. type IPBanInfo struct { + // RequireSASL indicates a "soft" ban; connections are allowed but they must SASL + RequireSASL bool // Reason is the ban reason. Reason string `json:"reason"` // OperReason is an oper ban reason. @@ -95,12 +97,13 @@ func (dm *DLineManager) AllBans() map[string]IPBanInfo { } // AddNetwork adds a network to the blocked list. -func (dm *DLineManager) AddNetwork(network flatip.IPNet, duration time.Duration, reason, operReason, operName string) error { +func (dm *DLineManager) AddNetwork(network flatip.IPNet, duration time.Duration, requireSASL bool, reason, operReason, operName string) error { dm.persistenceMutex.Lock() defer dm.persistenceMutex.Unlock() // assemble ban info info := IPBanInfo{ + RequireSASL: requireSASL, Reason: reason, OperReason: operReason, OperName: operName, diff --git a/irc/handlers.go b/irc/handlers.go index e92bc38b..b71e336a 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -818,7 +818,11 @@ func formatBanForListing(client *Client, key string, info IPBanInfo) string { if info.Duration != 0 { desc = fmt.Sprintf("%s [%s]", desc, info.TimeLeft()) } - return fmt.Sprintf(client.t("Ban - %[1]s - added by %[2]s - %[3]s"), key, info.OperName, desc) + banType := "Ban" + if info.RequireSASL { + banType = "SASL required" + } + return fmt.Sprintf(client.t("%[1]s - %[2]s - added by %[3]s - %[4]s"), banType, key, info.OperName, desc) } // DLINE [ANDKILL] [MYSELF] [duration] / [ON ] [reason [| oper reason]] @@ -906,7 +910,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res operName = server.name } - err = server.dlines.AddNetwork(flatip.FromNetIPNet(hostNet), duration, reason, operReason, operName) + err = server.dlines.AddNetwork(flatip.FromNetIPNet(hostNet), duration, false, reason, operReason, operName) if err != nil { rb.Notice(fmt.Sprintf(client.t("Could not successfully save new D-LINE: %s"), err.Error())) diff --git a/irc/help.go b/irc/help.go index 432b8100..5fa5fca2 100644 --- a/irc/help.go +++ b/irc/help.go @@ -518,13 +518,13 @@ given, views the current topic on the channel.`, Oragono's "unified ban" system. Accepts the following subcommands: -1. UBAN ADD [DURATION ] [REASON...] +1. UBAN ADD [REQUIRE-SASL] [DURATION ] [REASON...] 2. UBAN DEL 3. UBAN LIST 4. UBAN INFO may be an IP, a CIDR, a nickmask with wildcards, or the name of an -account to suspend.`, +account to suspend. Note that REQUIRE-SASL is only valid for IP and CIDR bans.`, }, "undline": { oper: true, diff --git a/irc/server.go b/irc/server.go index ed690e20..ed74f60f 100644 --- a/irc/server.go +++ b/irc/server.go @@ -179,8 +179,13 @@ func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool // check DLINEs isBanned, info := server.dlines.CheckIP(flat) if isBanned { - server.logger.Info("connect-ip", "Client rejected by d-line", ipaddr.String()) - return true, false, info.BanMessage("You are banned from this server (%s)") + if info.RequireSASL { + server.logger.Info("connect-ip", "Requiring SASL from client due to d-line", ipaddr.String()) + return false, true, info.BanMessage("You must authenticate with SASL to connect from this IP (%s)") + } else { + server.logger.Info("connect-ip", "Client rejected by d-line", ipaddr.String()) + return true, false, info.BanMessage("You are banned from this server (%s)") + } } // check connection limits @@ -202,14 +207,14 @@ func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool server.logger.Error("internal", "couldn't check IP ban script", ipaddr.String(), err.Error()) return false, false, "" } - // TODO: currently no way to cache results other than IPBanned - if output.Result == IPBanned && output.CacheSeconds != 0 { + // TODO: currently no way to cache IPAccepted + if (output.Result == IPBanned || output.Result == IPRequireSASL) && output.CacheSeconds != 0 { network, err := flatip.ParseToNormalizedNet(output.CacheNet) if err != nil { server.logger.Error("internal", "invalid dline net from IP ban script", ipaddr.String(), output.CacheNet) } else { dlineDuration := time.Duration(output.CacheSeconds) * time.Second - err := server.dlines.AddNetwork(network, dlineDuration, output.BanMessage, "", "") + err := server.dlines.AddNetwork(network, dlineDuration, output.Result == IPRequireSASL, output.BanMessage, "", "") if err != nil { server.logger.Error("internal", "couldn't set dline from IP ban script", ipaddr.String(), err.Error()) } diff --git a/irc/uban.go b/irc/uban.go index d631dda1..34777157 100644 --- a/irc/uban.go +++ b/irc/uban.go @@ -16,15 +16,24 @@ import ( "github.com/oragono/oragono/irc/utils" ) -func consumeDuration(params []string, rb *ResponseBuffer) (duration time.Duration, remainingParams []string, err error) { +func consumeDuration(params []string, rb *ResponseBuffer) (duration time.Duration, requireSASL bool, remainingParams []string, err error) { remainingParams = params - if 2 <= len(remainingParams) && strings.ToLower(remainingParams[0]) == "duration" { - duration, err = custime.ParseDuration(remainingParams[1]) - if err != nil { - rb.Notice(rb.session.client.t("Invalid time duration for NS SUSPEND")) - return + for { + if duration == 0 && 2 <= len(remainingParams) && strings.ToLower(remainingParams[0]) == "duration" { + duration, err = custime.ParseDuration(remainingParams[1]) + if err != nil { + rb.Notice(rb.session.client.t("Invalid time duration for NS SUSPEND")) + return + } + remainingParams = remainingParams[2:] + continue } - remainingParams = remainingParams[2:] + if !requireSASL && 1 <= len(remainingParams) && strings.ToLower(remainingParams[0]) == "require-sasl" { + requireSASL = true + remainingParams = remainingParams[1:] + continue + } + break } return } @@ -139,7 +148,7 @@ func sessionsForCIDR(server *Server, cidr flatip.IPNet, exclude *Session) (sessi } func ubanAddHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool { - duration, params, err := consumeDuration(params, rb) + duration, requireSASL, params, err := consumeDuration(params, rb) if err != nil { return false } @@ -148,7 +157,7 @@ func ubanAddHandler(client *Client, target ubanTarget, params []string, rb *Resp switch target.banType { case ubanCIDR: - ubanAddCIDR(client, target, duration, operReason, rb) + ubanAddCIDR(client, target, duration, requireSASL, operReason, rb) case ubanNickmask: ubanAddNickmask(client, target, duration, operReason, rb) case ubanNick: @@ -158,8 +167,8 @@ func ubanAddHandler(client *Client, target ubanTarget, params []string, rb *Resp return false } -func ubanAddCIDR(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) { - err := client.server.dlines.AddNetwork(target.cidr, duration, "", operReason, client.Oper().Name) +func ubanAddCIDR(client *Client, target ubanTarget, duration time.Duration, requireSASL bool, operReason string, rb *ResponseBuffer) { + err := client.server.dlines.AddNetwork(target.cidr, duration, requireSASL, "", operReason, client.Oper().Name) if err == nil { rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.cidr.HumanReadableString())) } else {