mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-10-31 13:57:23 +01:00 
			
		
		
		
	Merge pull request #1491 from slingamn/uban.3
UBAN and some other operator changes (fixes #1447)
This commit is contained in:
		
						commit
						62d1f884eb
					
				| @ -584,9 +584,8 @@ oper-classes: | ||||
| 
 | ||||
|         # capability names | ||||
|         capabilities: | ||||
|             - "local_kill" | ||||
|             - "local_ban" | ||||
|             - "local_unban" | ||||
|             - "kill" | ||||
|             - "ban" | ||||
|             - "nofakelag" | ||||
|             - "roleplay" | ||||
|             - "relaymsg" | ||||
|  | ||||
| @ -930,9 +930,8 @@ oper-classes: | ||||
| 
 | ||||
|         # capability names | ||||
|         capabilities: | ||||
|         - "local_kill" | ||||
|         - "local_ban" | ||||
|         - "local_unban" | ||||
|         - "kill" | ||||
|         - "ban" | ||||
|         - "nofakelag" | ||||
| 
 | ||||
| # ircd operators | ||||
|  | ||||
							
								
								
									
										109
									
								
								irc/chanserv.go
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								irc/chanserv.go
									
									
									
									
									
								
							| @ -175,6 +175,17 @@ SET modifies a channel's settings. The following settings are available:`, | ||||
| 			enabled:   chanregEnabled, | ||||
| 			minParams: 3, | ||||
| 		}, | ||||
| 		"howtoban": { | ||||
| 			handler:   csHowToBanHandler, | ||||
| 			helpShort: `$bHOWTOBAN$b suggests the best available way of banning a user`, | ||||
| 			help: `Syntax: $bHOWTOBAN #channel <nick> | ||||
| 
 | ||||
| The best way to ban a user from a channel will depend on how they are | ||||
| connected to the server. $bHOWTOBAN$b suggests a ban command that will | ||||
| (ideally) prevent the user from returning to the channel.`, | ||||
| 			enabled:   chanregEnabled, | ||||
| 			minParams: 2, | ||||
| 		}, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| @ -502,10 +513,13 @@ func csTransferHandler(service *ircService, server *Server, client *Client, comm | ||||
| 	chname = regInfo.Name | ||||
| 	account := client.Account() | ||||
| 	isFounder := account != "" && account == regInfo.Founder | ||||
| 	hasPrivs := client.HasRoleCapabs("chanreg") | ||||
| 	if !(isFounder || hasPrivs) { | ||||
| 		service.Notice(rb, client.t("Insufficient privileges")) | ||||
| 		return | ||||
| 	var oper *Oper | ||||
| 	if !isFounder { | ||||
| 		oper = client.Oper() | ||||
| 		if !oper.HasRoleCapab("chanreg") { | ||||
| 			service.Notice(rb, client.t("Insufficient privileges")) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	target := params[1] | ||||
| 	targetAccount, err := server.accounts.LoadAccount(params[1]) | ||||
| @ -522,7 +536,12 @@ func csTransferHandler(service *ircService, server *Server, client *Client, comm | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	status, err := channel.Transfer(client, target, hasPrivs) | ||||
| 	if !isFounder { | ||||
| 		message := fmt.Sprintf("Operator %s ran CS TRANSFER on %s to account %s", oper.Name, chname, target) | ||||
| 		server.snomasks.Send(sno.LocalOpers, message) | ||||
| 		server.logger.Info("opers", message) | ||||
| 	} | ||||
| 	status, err := channel.Transfer(client, target, oper != nil) | ||||
| 	if err == nil { | ||||
| 		switch status { | ||||
| 		case channelTransferComplete: | ||||
| @ -801,3 +820,83 @@ func csSetHandler(service *ircService, server *Server, client *Client, command s | ||||
| 		service.Notice(rb, client.t("An error occurred")) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func csHowToBanHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { | ||||
| 	success := false | ||||
| 	defer func() { | ||||
| 		if success { | ||||
| 			service.Notice(rb, client.t("Note that if the user is currently in the channel, you must /KICK them after you ban them")) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	chname, nick := params[0], params[1] | ||||
| 	channel := server.channels.Get(chname) | ||||
| 	if channel == nil { | ||||
| 		service.Notice(rb, client.t("No such channel")) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if !channel.ClientIsAtLeast(client, modes.Operator) || client.HasRoleCapabs("samode") { | ||||
| 		service.Notice(rb, client.t("Insufficient privileges")) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	var details WhoWas | ||||
| 	target := server.clients.Get(nick) | ||||
| 	if target == nil { | ||||
| 		whowasList := server.whoWas.Find(nick, 1) | ||||
| 		if len(whowasList) == 0 { | ||||
| 			service.Notice(rb, client.t("No such nick")) | ||||
| 			return | ||||
| 		} | ||||
| 		service.Notice(rb, fmt.Sprintf(client.t("Warning: %s is not currently connected to the server. Using WHOWAS data, which may be inaccurate:"), nick)) | ||||
| 		details = whowasList[0] | ||||
| 	} else { | ||||
| 		details = target.Details().WhoWas | ||||
| 	} | ||||
| 
 | ||||
| 	if details.account != "" { | ||||
| 		if channel.getAmode(details.account) != modes.Mode(0) { | ||||
| 			service.Notice(rb, fmt.Sprintf(client.t("Warning: account %s currently has a persistent channel privilege granted with CS AMODE. If this mode is not removed, bans will not be respected"), details.accountName)) | ||||
| 			return | ||||
| 		} else if details.account == channel.Founder() { | ||||
| 			service.Notice(rb, fmt.Sprintf(client.t("Warning: account %s is the channel founder and cannot be banned"), details.accountName)) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	config := server.Config() | ||||
| 	if !config.Server.Cloaks.EnabledForAlwaysOn { | ||||
| 		service.Notice(rb, client.t("Warning: server.ip-cloaking.enabled-for-always-on is disabled. This reduces the precision of channel bans.")) | ||||
| 	} | ||||
| 
 | ||||
| 	if details.account != "" { | ||||
| 		if config.Accounts.NickReservation.ForceNickEqualsAccount || target.AlwaysOn() { | ||||
| 			service.Notice(rb, fmt.Sprintf(client.t("User %[1]s is authenticated and can be banned by nickname: /MODE %[2]s +b %[3]s!*@*"), details.nick, channel.Name(), details.nick)) | ||||
| 			success = true | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ban := fmt.Sprintf("*!*@%s", strings.ToLower(details.hostname)) | ||||
| 	banRe, err := utils.CompileGlob(ban, false) | ||||
| 	if err != nil { | ||||
| 		server.logger.Error("internal", "couldn't compile ban regex", ban, err.Error()) | ||||
| 		service.Notice(rb, "An error occurred") | ||||
| 		return | ||||
| 	} | ||||
| 	var collateralDamage []string | ||||
| 	for _, mcl := range channel.Members() { | ||||
| 		if mcl != target && banRe.MatchString(mcl.NickMaskCasefolded()) { | ||||
| 			collateralDamage = append(collateralDamage, mcl.Nick()) | ||||
| 		} | ||||
| 	} | ||||
| 	service.Notice(rb, fmt.Sprintf(client.t("User %[1]s can be banned by hostname: /MODE %[2]s +b %[3]s"), details.nick, channel.Name(), ban)) | ||||
| 	success = true | ||||
| 	if len(collateralDamage) != 0 { | ||||
| 		service.Notice(rb, fmt.Sprintf(client.t("Warning: this ban will affect %d other users:"), len(collateralDamage))) | ||||
| 		for _, line := range utils.BuildTokenLines(400, collateralDamage, " ") { | ||||
| 			service.Notice(rb, line) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -292,6 +292,9 @@ type WhoWas struct { | ||||
| 	username       string | ||||
| 	hostname       string | ||||
| 	realname       string | ||||
| 	// technically not required for WHOWAS: | ||||
| 	account     string | ||||
| 	accountName string | ||||
| } | ||||
| 
 | ||||
| // ClientDetails is a standard set of details about a client | ||||
| @ -300,8 +303,6 @@ type ClientDetails struct { | ||||
| 
 | ||||
| 	nickMask           string | ||||
| 	nickMaskCasefolded string | ||||
| 	account            string | ||||
| 	accountName        string | ||||
| } | ||||
| 
 | ||||
| // RunClient sets up a new client and runs its goroutine. | ||||
|  | ||||
| @ -170,7 +170,7 @@ func init() { | ||||
| 			handler:   killHandler, | ||||
| 			minParams: 1, | ||||
| 			oper:      true, | ||||
| 			capabs:    []string{"local_kill"}, //TODO(dan): when we have S2S, this will be checked in the command handler itself | ||||
| 			capabs:    []string{"kill"}, | ||||
| 		}, | ||||
| 		"KLINE": { | ||||
| 			handler:   klineHandler, | ||||
| @ -319,6 +319,11 @@ func init() { | ||||
| 			handler:   topicHandler, | ||||
| 			minParams: 1, | ||||
| 		}, | ||||
| 		"UBAN": { | ||||
| 			handler:   ubanHandler, | ||||
| 			minParams: 1, | ||||
| 			capabs:    []string{"ban"}, | ||||
| 		}, | ||||
| 		"UNDLINE": { | ||||
| 			handler:   unDLineHandler, | ||||
| 			minParams: 1, | ||||
|  | ||||
| @ -649,7 +649,7 @@ type OperClass struct { | ||||
| // OperatorClasses returns a map of assembled operator classes from the given config. | ||||
| func (conf *Config) OperatorClasses() (map[string]*OperClass, error) { | ||||
| 	fixupCapability := func(capab string) string { | ||||
| 		return strings.TrimPrefix(capab, "oper:") // #868 | ||||
| 		return strings.TrimPrefix(strings.TrimPrefix(capab, "oper:"), "local_") // #868, #1442 | ||||
| 	} | ||||
| 
 | ||||
| 	ocs := make(map[string]*OperClass) | ||||
| @ -733,6 +733,10 @@ type Oper struct { | ||||
| 	Modes     []modes.ModeChange | ||||
| } | ||||
| 
 | ||||
| func (oper *Oper) HasRoleCapab(capab string) bool { | ||||
| 	return oper != nil && oper.Class.Capabilities.Has(capab) | ||||
| } | ||||
| 
 | ||||
| // Operators returns a map of operator configs from the given OperClass and config. | ||||
| func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error) { | ||||
| 	operators := make(map[string]*Oper) | ||||
|  | ||||
| @ -209,6 +209,38 @@ func (cl *Limiter) RemoveClient(addr flatip.IP) { | ||||
| 	cl.limiter[addrString] = count | ||||
| } | ||||
| 
 | ||||
| type LimiterStatus struct { | ||||
| 	Exempt bool | ||||
| 
 | ||||
| 	Count    int | ||||
| 	MaxCount int | ||||
| 
 | ||||
| 	Throttle         int | ||||
| 	MaxPerWindow     int | ||||
| 	ThrottleDuration time.Duration | ||||
| } | ||||
| 
 | ||||
| func (cl *Limiter) Status(addr flatip.IP) (status LimiterStatus) { | ||||
| 	cl.Lock() | ||||
| 	defer cl.Unlock() | ||||
| 
 | ||||
| 	if flatip.IPInNets(addr, cl.config.exemptedNets) { | ||||
| 		status.Exempt = true | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	status.ThrottleDuration = cl.config.Window | ||||
| 
 | ||||
| 	addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr) | ||||
| 	status.MaxCount = maxConcurrent | ||||
| 	status.MaxPerWindow = maxPerWindow | ||||
| 
 | ||||
| 	status.Count = cl.limiter[addrString] | ||||
| 	status.Throttle = cl.throttler[addrString].Count | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // ResetThrottle resets the throttle count for an IP | ||||
| func (cl *Limiter) ResetThrottle(addr flatip.IP) { | ||||
| 	cl.Lock() | ||||
|  | ||||
							
								
								
									
										35
									
								
								irc/dline.go
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								irc/dline.go
									
									
									
									
									
								
							| @ -6,13 +6,11 @@ package irc | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/oragono/oragono/irc/flatip" | ||||
| 	"github.com/oragono/oragono/irc/utils" | ||||
| 	"github.com/tidwall/buntdb" | ||||
| ) | ||||
| 
 | ||||
| @ -48,7 +46,11 @@ func (info IPBanInfo) TimeLeft() string { | ||||
| 
 | ||||
| // BanMessage returns the ban message. | ||||
| func (info IPBanInfo) BanMessage(message string) string { | ||||
| 	message = fmt.Sprintf(message, info.Reason) | ||||
| 	reason := info.Reason | ||||
| 	if reason == "" { | ||||
| 		reason = "No reason given" | ||||
| 	} | ||||
| 	message = fmt.Sprintf(message, reason) | ||||
| 	if info.Duration != 0 { | ||||
| 		message += fmt.Sprintf(" [%s]", info.TimeLeft()) | ||||
| 	} | ||||
| @ -86,14 +88,14 @@ func (dm *DLineManager) AllBans() map[string]IPBanInfo { | ||||
| 	defer dm.RUnlock() | ||||
| 
 | ||||
| 	for key, info := range dm.networks { | ||||
| 		allb[key.String()] = info | ||||
| 		allb[key.HumanReadableString()] = info | ||||
| 	} | ||||
| 
 | ||||
| 	return allb | ||||
| } | ||||
| 
 | ||||
| // AddNetwork adds a network to the blocked list. | ||||
| func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, reason, operReason, operName string) error { | ||||
| func (dm *DLineManager) AddNetwork(network flatip.IPNet, duration time.Duration, reason, operReason, operName string) error { | ||||
| 	dm.persistenceMutex.Lock() | ||||
| 	defer dm.persistenceMutex.Unlock() | ||||
| 
 | ||||
| @ -110,8 +112,7 @@ func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, re | ||||
| 	return dm.persistDline(id, info) | ||||
| } | ||||
| 
 | ||||
| func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (id flatip.IPNet) { | ||||
| 	flatnet := flatip.FromNetIPNet(network) | ||||
| func (dm *DLineManager) addNetworkInternal(flatnet flatip.IPNet, info IPBanInfo) (id flatip.IPNet) { | ||||
| 	id = flatnet | ||||
| 
 | ||||
| 	var timeLeft time.Duration | ||||
| @ -193,11 +194,11 @@ func (dm *DLineManager) unpersistDline(id flatip.IPNet) error { | ||||
| } | ||||
| 
 | ||||
| // RemoveNetwork removes a network from the blocked list. | ||||
| func (dm *DLineManager) RemoveNetwork(network net.IPNet) error { | ||||
| func (dm *DLineManager) RemoveNetwork(network flatip.IPNet) error { | ||||
| 	dm.persistenceMutex.Lock() | ||||
| 	defer dm.persistenceMutex.Unlock() | ||||
| 
 | ||||
| 	id := flatip.FromNetIPNet(network) | ||||
| 	id := network | ||||
| 
 | ||||
| 	present := func() bool { | ||||
| 		dm.Lock() | ||||
| @ -215,22 +216,8 @@ func (dm *DLineManager) RemoveNetwork(network net.IPNet) error { | ||||
| 	return dm.unpersistDline(id) | ||||
| } | ||||
| 
 | ||||
| // AddIP adds an IP address to the blocked list. | ||||
| func (dm *DLineManager) AddIP(addr net.IP, duration time.Duration, reason, operReason, operName string) error { | ||||
| 	return dm.AddNetwork(utils.NormalizeIPToNet(addr), duration, reason, operReason, operName) | ||||
| } | ||||
| 
 | ||||
| // RemoveIP removes an IP address from the blocked list. | ||||
| func (dm *DLineManager) RemoveIP(addr net.IP) error { | ||||
| 	return dm.RemoveNetwork(utils.NormalizeIPToNet(addr)) | ||||
| } | ||||
| 
 | ||||
| // CheckIP returns whether or not an IP address was banned, and how long it is banned for. | ||||
| func (dm *DLineManager) CheckIP(addr flatip.IP) (isBanned bool, info IPBanInfo) { | ||||
| 	if addr.IsLoopback() { | ||||
| 		return // #671 | ||||
| 	} | ||||
| 
 | ||||
| 	dm.RLock() | ||||
| 	defer dm.RUnlock() | ||||
| 
 | ||||
| @ -257,7 +244,7 @@ func (dm *DLineManager) loadFromDatastore() { | ||||
| 			key = strings.TrimPrefix(key, dlinePrefix) | ||||
| 
 | ||||
| 			// load addr/net | ||||
| 			hostNet, err := utils.NormalizedNetFromString(key) | ||||
| 			hostNet, err := flatip.ParseToNormalizedNet(key) | ||||
| 			if err != nil { | ||||
| 				dm.server.logger.Error("internal", "bad dline cidr", err.Error()) | ||||
| 				return true | ||||
|  | ||||
| @ -183,6 +183,16 @@ func (cidr IPNet) String() string { | ||||
| 	return ipnet.String() | ||||
| } | ||||
| 
 | ||||
| // HumanReadableString returns a string representation of an IPNet; | ||||
| // if the network contains only a single IP address, it returns | ||||
| // a representation of that address. | ||||
| func (cidr IPNet) HumanReadableString() string { | ||||
| 	if cidr.PrefixLen == 128 { | ||||
| 		return cidr.IP.String() | ||||
| 	} | ||||
| 	return cidr.String() | ||||
| } | ||||
| 
 | ||||
| // IsZero tests whether ipnet is the zero value of an IPNet, 0::0/0. | ||||
| // Although this is a valid subnet, it can still be used as a sentinel | ||||
| // value in some contexts. | ||||
|  | ||||
| @ -314,6 +314,12 @@ func (client *Client) setCloakedHostname(cloak string) { | ||||
| 	client.updateNickMaskNoMutex() | ||||
| } | ||||
| 
 | ||||
| func (client *Client) CloakedHostname() string { | ||||
| 	client.stateMutex.Lock() | ||||
| 	defer client.stateMutex.Unlock() | ||||
| 	return client.cloakedHostname | ||||
| } | ||||
| 
 | ||||
| func (client *Client) historyCutoff() (cutoff time.Time) { | ||||
| 	client.stateMutex.Lock() | ||||
| 	if client.account != "" { | ||||
| @ -553,3 +559,9 @@ func (channel *Channel) Ctime() (ctime time.Time) { | ||||
| 	channel.stateMutex.RUnlock() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (channel *Channel) getAmode(cfaccount string) (result modes.Mode) { | ||||
| 	channel.stateMutex.RLock() | ||||
| 	defer channel.stateMutex.RUnlock() | ||||
| 	return channel.accountToUMode[cfaccount] | ||||
| } | ||||
|  | ||||
| @ -826,7 +826,7 @@ func formatBanForListing(client *Client, key string, info IPBanInfo) string { | ||||
| func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { | ||||
| 	// check oper permissions | ||||
| 	oper := client.Oper() | ||||
| 	if oper == nil || !oper.Class.Capabilities.Has("local_ban") { | ||||
| 	if !oper.HasRoleCapab("ban") { | ||||
| 		rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs")) | ||||
| 		return false | ||||
| 	} | ||||
| @ -906,7 +906,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res | ||||
| 		operName = server.name | ||||
| 	} | ||||
| 
 | ||||
| 	err = server.dlines.AddNetwork(hostNet, duration, reason, operReason, operName) | ||||
| 	err = server.dlines.AddNetwork(flatip.FromNetIPNet(hostNet), duration, reason, operReason, operName) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Could not successfully save new D-LINE: %s"), err.Error())) | ||||
| @ -1273,6 +1273,10 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	message := fmt.Sprintf("Operator %s ran SAJOIN %s", client.Oper().Name, strings.Join(msg.Params, " ")) | ||||
| 	server.snomasks.Send(sno.LocalOpers, message) | ||||
| 	server.logger.Info("opers", message) | ||||
| 
 | ||||
| 	channels := strings.Split(channelString, ",") | ||||
| 	for _, chname := range channels { | ||||
| 		err, _ := server.channels.Join(target, chname, "", true, rb) | ||||
| @ -1364,7 +1368,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res | ||||
| 	details := client.Details() | ||||
| 	// check oper permissions | ||||
| 	oper := client.Oper() | ||||
| 	if oper == nil || !oper.Class.Capabilities.Has("local_ban") { | ||||
| 	if !oper.HasRoleCapab("ban") { | ||||
| 		rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs")) | ||||
| 		return false | ||||
| 	} | ||||
| @ -1737,6 +1741,12 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if msg.Command == "SAMODE" { | ||||
| 		message := fmt.Sprintf("Operator %s ran SAMODE %s", client.Oper().Name, strings.Join(msg.Params, " ")) | ||||
| 		server.snomasks.Send(sno.LocalOpers, message) | ||||
| 		server.logger.Info("opers", message) | ||||
| 	} | ||||
| 
 | ||||
| 	// applied mode changes | ||||
| 	applied := make(modes.ModeChanges, 0) | ||||
| 
 | ||||
| @ -2307,6 +2317,7 @@ func applyOper(client *Client, oper *Oper, rb *ResponseBuffer) { | ||||
| 		copy(modeChanges[1:], oper.Modes) | ||||
| 		applied := ApplyUserModeChanges(client, modeChanges, true, oper) | ||||
| 
 | ||||
| 		client.server.logger.Info("opers", details.nick, "opered up as", oper.Name) | ||||
| 		client.server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]"), newDetails.nickMask, oper.Name)) | ||||
| 
 | ||||
| 		rb.Broadcast(nil, client.server.name, RPL_YOUREOPER, details.nick, client.t("You are now an IRC operator")) | ||||
| @ -2814,7 +2825,7 @@ func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res | ||||
| func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { | ||||
| 	// check oper permissions | ||||
| 	oper := client.Oper() | ||||
| 	if oper == nil || !oper.Class.Capabilities.Has("local_unban") { | ||||
| 	if !oper.HasRoleCapab("ban") { | ||||
| 		rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs")) | ||||
| 		return false | ||||
| 	} | ||||
| @ -2822,13 +2833,8 @@ func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R | ||||
| 	// get host | ||||
| 	hostString := msg.Params[0] | ||||
| 
 | ||||
| 	// TODO(#1447) consolidate this into the "unban" command | ||||
| 	if flatip, ipErr := flatip.ParseIP(hostString); ipErr == nil { | ||||
| 		server.connectionLimiter.ResetThrottle(flatip) | ||||
| 	} | ||||
| 
 | ||||
| 	// check host | ||||
| 	hostNet, err := utils.NormalizedNetFromString(hostString) | ||||
| 	hostNet, err := flatip.ParseToNormalizedNet(hostString) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Could not parse IP address or CIDR network")) | ||||
| @ -2842,7 +2848,7 @@ func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	hostString = utils.NetToNormalizedString(hostNet) | ||||
| 	hostString = hostNet.String() | ||||
| 	rb.Notice(fmt.Sprintf(client.t("Removed D-Line for %s"), hostString)) | ||||
| 	server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed D-Line for %s"), client.nick, hostString)) | ||||
| 	return false | ||||
| @ -2853,7 +2859,7 @@ func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R | ||||
| 	details := client.Details() | ||||
| 	// check oper permissions | ||||
| 	oper := client.Oper() | ||||
| 	if oper == nil || !oper.Class.Capabilities.Has("local_unban") { | ||||
| 	if !oper.HasRoleCapab("ban") { | ||||
| 		rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs")) | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										13
									
								
								irc/help.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								irc/help.go
									
									
									
									
									
								
							| @ -520,6 +520,19 @@ Shows the time of the current, or the given, server.`, | ||||
| 
 | ||||
| If [topic] is given, sets the topic in the channel to that. If [topic] is not | ||||
| given, views the current topic on the channel.`, | ||||
| 	}, | ||||
| 	"uban": { | ||||
| 		text: `UBAN <subcommand> [arguments] | ||||
| 
 | ||||
| Oragono's "unified ban" system. Accepts the following subcommands: | ||||
| 
 | ||||
| 1. UBAN ADD <target> [DURATION <duration>] [REASON...] | ||||
| 2. UBAN DEL <target> | ||||
| 3. UBAN LIST | ||||
| 4. UBAN INFO <target> | ||||
| 
 | ||||
| <target> may be an IP, a CIDR, a nickmask with wildcards, or the name of an | ||||
| account to suspend.`, | ||||
| 	}, | ||||
| 	"undline": { | ||||
| 		oper: true, | ||||
|  | ||||
| @ -1,3 +1,6 @@ | ||||
| // Copyright (c) 2020 Shivaram Lingamneni | ||||
| // released under the MIT license | ||||
| 
 | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
|  | ||||
							
								
								
									
										11
									
								
								irc/kline.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								irc/kline.go
									
									
									
									
									
								
							| @ -189,6 +189,17 @@ func (km *KLineManager) RemoveMask(mask string) error { | ||||
| 	return km.unpersistKLine(mask) | ||||
| } | ||||
| 
 | ||||
| func (km *KLineManager) ContainsMask(mask string) (isBanned bool, info IPBanInfo) { | ||||
| 	km.RLock() | ||||
| 	defer km.RUnlock() | ||||
| 
 | ||||
| 	klineInfo, isBanned := km.entries[mask] | ||||
| 	if isBanned { | ||||
| 		info = klineInfo.Info | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // CheckMasks returns whether or not the hostmask(s) are banned, and how long they are banned for. | ||||
| func (km *KLineManager) CheckMasks(masks ...string) (isBanned bool, info IPBanInfo) { | ||||
| 	km.RLock() | ||||
|  | ||||
| @ -995,17 +995,21 @@ func nsPasswdHandler(service *ircService, server *Server, client *Client, comman | ||||
| 	var newPassword string | ||||
| 	var errorMessage string | ||||
| 
 | ||||
| 	hasPrivs := client.HasRoleCapabs("accreg") | ||||
| 	var oper *Oper | ||||
| 
 | ||||
| 	switch len(params) { | ||||
| 	case 2: | ||||
| 		if !hasPrivs { | ||||
| 		oper = client.Oper() | ||||
| 		if !oper.HasRoleCapab("accreg") { | ||||
| 			errorMessage = `Insufficient privileges` | ||||
| 		} else { | ||||
| 			target, newPassword = params[0], params[1] | ||||
| 			if newPassword == "*" { | ||||
| 				newPassword = "" | ||||
| 			} | ||||
| 			message := fmt.Sprintf("Operator %s ran NS PASSWD for account %s", oper.Name, target) | ||||
| 			server.snomasks.Send(sno.LocalOpers, message) | ||||
| 			server.logger.Info("opers", message) | ||||
| 		} | ||||
| 	case 3: | ||||
| 		target = client.Account() | ||||
| @ -1041,7 +1045,7 @@ func nsPasswdHandler(service *ircService, server *Server, client *Client, comman | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	err := server.accounts.setPassword(target, newPassword, hasPrivs) | ||||
| 	err := server.accounts.setPassword(target, newPassword, oper != nil) | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		service.Notice(rb, client.t("Password changed")) | ||||
| @ -1090,7 +1094,7 @@ func nsClientsHandler(service *ircService, server *Server, client *Client, comma | ||||
| 
 | ||||
| func nsClientsListHandler(service *ircService, server *Server, client *Client, params []string, rb *ResponseBuffer) { | ||||
| 	target := client | ||||
| 	hasPrivs := client.HasRoleCapabs("local_ban") | ||||
| 	hasPrivs := client.HasRoleCapabs("ban") | ||||
| 	if 0 < len(params) { | ||||
| 		target = server.clients.Get(params[0]) | ||||
| 		if target == nil { | ||||
| @ -1141,10 +1145,10 @@ func nsClientsLogoutHandler(service *ircService, server *Server, client *Client, | ||||
| 			service.Notice(rb, client.t("No such nick")) | ||||
| 			return | ||||
| 		} | ||||
| 		// User must have "local_kill" privileges to logout other user sessions. | ||||
| 		// User must have "kill" privileges to logout other user sessions. | ||||
| 		if target != client { | ||||
| 			oper := client.Oper() | ||||
| 			if oper == nil || !oper.Class.Capabilities.Has("local_kill") { | ||||
| 			if oper.HasRoleCapab("kill") { | ||||
| 				service.Notice(rb, client.t("Insufficient oper privs")) | ||||
| 				return | ||||
| 			} | ||||
| @ -1357,11 +1361,16 @@ func (a ByCreationTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||
| func (a ByCreationTime) Less(i, j int) bool { return a[i].TimeCreated.After(a[j].TimeCreated) } | ||||
| 
 | ||||
| func nsSuspendListHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { | ||||
| 	suspensions := server.accounts.ListSuspended() | ||||
| 	listAccountSuspensions(client, rb, service.prefix) | ||||
| } | ||||
| 
 | ||||
| func listAccountSuspensions(client *Client, rb *ResponseBuffer, source string) { | ||||
| 	suspensions := client.server.accounts.ListSuspended() | ||||
| 	sort.Sort(ByCreationTime(suspensions)) | ||||
| 	service.Notice(rb, fmt.Sprintf(client.t("There are %d active suspensions."), len(suspensions))) | ||||
| 	nick := client.Nick() | ||||
| 	rb.Add(nil, source, "NOTICE", nick, fmt.Sprintf(client.t("There are %d active account suspensions."), len(suspensions))) | ||||
| 	for _, suspension := range suspensions { | ||||
| 		service.Notice(rb, suspensionToString(client, suspension)) | ||||
| 		rb.Add(nil, source, "NOTICE", nick, suspensionToString(client, suspension)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -162,8 +162,14 @@ func (server *Server) Run() { | ||||
| } | ||||
| 
 | ||||
| func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool) (banned bool, requireSASL bool, message string) { | ||||
| 	// #671: do not enforce bans against loopback, as a failsafe | ||||
| 	// note that this function is not used for Tor connections (checkTorLimits is used instead) | ||||
| 	if ipaddr.IsLoopback() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if server.Defcon() == 1 { | ||||
| 		if !(ipaddr.IsLoopback() || utils.IPInNets(ipaddr, server.Config().Server.secureNets)) { | ||||
| 		if !utils.IPInNets(ipaddr, server.Config().Server.secureNets) { | ||||
| 			return true, false, "New connections to this server are temporarily restricted" | ||||
| 		} | ||||
| 	} | ||||
| @ -198,7 +204,7 @@ func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool | ||||
| 		} | ||||
| 		// TODO: currently no way to cache results other than IPBanned | ||||
| 		if output.Result == IPBanned && output.CacheSeconds != 0 { | ||||
| 			network, err := utils.NormalizedNetFromString(output.CacheNet) | ||||
| 			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 { | ||||
| @ -339,11 +345,13 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { | ||||
| 	// count new user in statistics (before checking KLINEs, see #1303) | ||||
| 	server.stats.Register(c.HasMode(modes.Invisible)) | ||||
| 
 | ||||
| 	// check KLINEs | ||||
| 	isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...) | ||||
| 	if isBanned { | ||||
| 		c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil) | ||||
| 		return true | ||||
| 	// check KLINEs (#671: ignore KLINEs for loopback connections) | ||||
| 	if !session.IP().IsLoopback() || session.isTor { | ||||
| 		isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...) | ||||
| 		if isBanned { | ||||
| 			c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	server.playRegistrationBurst(session) | ||||
|  | ||||
							
								
								
									
										398
									
								
								irc/uban.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								irc/uban.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,398 @@ | ||||
| // Copyright (c) 2021 Shivaram Lingamneni | ||||
| // released under the MIT license | ||||
| 
 | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/goshuirc/irc-go/ircmsg" | ||||
| 
 | ||||
| 	"github.com/oragono/oragono/irc/custime" | ||||
| 	"github.com/oragono/oragono/irc/flatip" | ||||
| 	"github.com/oragono/oragono/irc/utils" | ||||
| ) | ||||
| 
 | ||||
| func consumeDuration(params []string, rb *ResponseBuffer) (duration time.Duration, 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 | ||||
| 		} | ||||
| 		remainingParams = remainingParams[2:] | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // a UBAN target is one of these syntactically unambiguous entities: | ||||
| // an IP, a CIDR, a NUH mask, or an account name | ||||
| type ubanType uint | ||||
| 
 | ||||
| const ( | ||||
| 	ubanCIDR ubanType = iota | ||||
| 	ubanNickmask | ||||
| 	ubanNick | ||||
| ) | ||||
| 
 | ||||
| // tagged union, i guess | ||||
| type ubanTarget struct { | ||||
| 	banType ubanType | ||||
| 
 | ||||
| 	cidr       flatip.IPNet | ||||
| 	matcher    *regexp.Regexp | ||||
| 	nickOrMask string | ||||
| } | ||||
| 
 | ||||
| func parseUbanTarget(param string) (target ubanTarget, err error) { | ||||
| 	if utils.SafeErrorParam(param) == "*" { | ||||
| 		err = errInvalidParams | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ipnet, ipErr := flatip.ParseToNormalizedNet(param) | ||||
| 	if ipErr == nil { | ||||
| 		target.banType = ubanCIDR | ||||
| 		target.cidr = ipnet | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if strings.IndexByte(param, '!') != -1 || strings.IndexByte(param, '@') != -1 { | ||||
| 		canonicalized, cErr := CanonicalizeMaskWildcard(param) | ||||
| 		if cErr != nil { | ||||
| 			err = errInvalidParams | ||||
| 			return | ||||
| 		} | ||||
| 		re, reErr := utils.CompileGlob(canonicalized, false) | ||||
| 		if reErr != nil { | ||||
| 			err = errInvalidParams | ||||
| 			return | ||||
| 		} | ||||
| 		target.banType = ubanNickmask | ||||
| 		target.nickOrMask = canonicalized | ||||
| 		target.matcher = re | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if _, cErr := CasefoldName(param); cErr == nil { | ||||
| 		target.banType = ubanNick | ||||
| 		target.nickOrMask = param | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	err = errInvalidParams | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // UBAN <subcommand> [target] [DURATION <duration>] [reason...] | ||||
| func ubanHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { | ||||
| 	subcommand := strings.ToLower(msg.Params[0]) | ||||
| 	params := msg.Params[1:] | ||||
| 	var target ubanTarget | ||||
| 	if subcommand != "list" { | ||||
| 		if len(msg.Params) == 1 { | ||||
| 			rb.Add(nil, client.server.name, "FAIL", "UBAN", "INVALID_PARAMS", client.t("Not enough parameters")) | ||||
| 			return false | ||||
| 		} | ||||
| 		var parseErr error | ||||
| 		target, parseErr = parseUbanTarget(params[0]) | ||||
| 		if parseErr != nil { | ||||
| 			rb.Add(nil, client.server.name, "FAIL", "UBAN", "INVALID_PARAMS", client.t("Couldn't parse ban target")) | ||||
| 			return false | ||||
| 		} | ||||
| 		params = params[1:] | ||||
| 	} | ||||
| 
 | ||||
| 	switch subcommand { | ||||
| 	case "add": | ||||
| 		return ubanAddHandler(client, target, params, rb) | ||||
| 	case "del", "remove", "rm": | ||||
| 		return ubanDelHandler(client, target, params, rb) | ||||
| 	case "list": | ||||
| 		return ubanListHandler(client, params, rb) | ||||
| 	case "info": | ||||
| 		return ubanInfoHandler(client, target, params, rb) | ||||
| 	default: | ||||
| 		rb.Add(nil, server.name, "FAIL", "UBAN", "UNKNOWN_COMMAND", client.t("Unknown command")) | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func sessionsForCIDR(server *Server, cidr flatip.IPNet, exclude *Session) (sessions []*Session, nicks []string) { | ||||
| 	for _, client := range server.clients.AllClients() { | ||||
| 		for _, session := range client.Sessions() { | ||||
| 			seen := false | ||||
| 			if session != exclude && cidr.Contains(flatip.FromNetIP(session.IP())) { | ||||
| 				sessions = append(sessions, session) | ||||
| 				if !seen { | ||||
| 					seen = true | ||||
| 					nicks = append(nicks, session.client.Nick()) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func ubanAddHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool { | ||||
| 	duration, params, err := consumeDuration(params, rb) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	operReason := strings.Join(params, " ") | ||||
| 
 | ||||
| 	switch target.banType { | ||||
| 	case ubanCIDR: | ||||
| 		ubanAddCIDR(client, target, duration, operReason, rb) | ||||
| 	case ubanNickmask: | ||||
| 		ubanAddNickmask(client, target, duration, operReason, rb) | ||||
| 	case ubanNick: | ||||
| 		ubanAddAccount(client, target, duration, operReason, rb) | ||||
| 
 | ||||
| 	} | ||||
| 	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) | ||||
| 	if err == nil { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.cidr.HumanReadableString())) | ||||
| 	} else { | ||||
| 		client.server.logger.Error("internal", "ubanAddCIDR failed", err.Error()) | ||||
| 		rb.Notice(client.t("An error occurred")) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	sessions, nicks := sessionsForCIDR(client.server, target.cidr, rb.session) | ||||
| 	for _, session := range sessions { | ||||
| 		session.client.Quit("You have been banned from this server", session) | ||||
| 		session.client.destroy(session) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(sessions) != 0 { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Killed %[1]d active client(s) from %[2]s, associated with %[3]d nickname(s):"), len(sessions), target.cidr.String(), len(nicks))) | ||||
| 		for _, line := range utils.BuildTokenLines(400, nicks, " ") { | ||||
| 			rb.Notice(line) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ubanAddNickmask(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) { | ||||
| 	err := client.server.klines.AddMask(target.nickOrMask, duration, "", operReason, client.Oper().Name) | ||||
| 	if err == nil { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.nickOrMask)) | ||||
| 	} else { | ||||
| 		client.server.logger.Error("internal", "ubanAddNickmask failed", err.Error()) | ||||
| 		rb.Notice(client.t("An error occurred")) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	var killed []string | ||||
| 	var alwaysOn []string | ||||
| 	for _, mcl := range client.server.clients.AllClients() { | ||||
| 		if mcl != client && target.matcher.MatchString(client.NickMaskCasefolded()) { | ||||
| 			if !mcl.AlwaysOn() { | ||||
| 				killed = append(killed, mcl.Nick()) | ||||
| 				mcl.destroy(nil) | ||||
| 			} else { | ||||
| 				alwaysOn = append(alwaysOn, mcl.Nick()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if len(killed) != 0 { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Killed %d clients:"), len(killed))) | ||||
| 		for _, line := range utils.BuildTokenLines(400, killed, " ") { | ||||
| 			rb.Notice(line) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(alwaysOn) != 0 { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Warning: %d clients matched this rule, but were not killed due to being always-on:"), len(alwaysOn))) | ||||
| 		for _, line := range utils.BuildTokenLines(400, alwaysOn, " ") { | ||||
| 			rb.Notice(line) | ||||
| 		} | ||||
| 		rb.Notice(client.t("You can suspend their accounts instead; try /UBAN ADD <nickname>")) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ubanAddAccount(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) { | ||||
| 	account := target.nickOrMask | ||||
| 	// TODO this doesn't enumerate all sessions if ForceNickEqualsAccount is disabled | ||||
| 	var sessionData []SessionData | ||||
| 	if mcl := client.server.clients.Get(account); mcl != nil { | ||||
| 		sessionData, _ = mcl.AllSessionData(nil, true) | ||||
| 	} | ||||
| 
 | ||||
| 	err := client.server.accounts.Suspend(account, duration, client.Oper().Name, operReason) | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Successfully suspended account %s"), account)) | ||||
| 		if len(sessionData) != 0 { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("Disconnected %d client(s) associated with the account, using the following IPs:"), len(sessionData))) | ||||
| 			for i, d := range sessionData { | ||||
| 				rb.Notice(fmt.Sprintf("%d. %s", i+1, d.ip.String())) | ||||
| 			} | ||||
| 		} | ||||
| 	case errAccountDoesNotExist: | ||||
| 		rb.Notice(client.t("No such account")) | ||||
| 	default: | ||||
| 		rb.Notice(client.t("An error occurred")) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ubanDelHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool { | ||||
| 	var err error | ||||
| 	var targetString string | ||||
| 	switch target.banType { | ||||
| 	case ubanCIDR: | ||||
| 		if target.cidr.PrefixLen == 128 { | ||||
| 			client.server.connectionLimiter.ResetThrottle(target.cidr.IP) | ||||
| 			rb.Notice(fmt.Sprintf(client.t("Reset throttle for IP: %s"), target.cidr.IP.String())) | ||||
| 		} | ||||
| 		targetString = target.cidr.HumanReadableString() | ||||
| 		err = client.server.dlines.RemoveNetwork(target.cidr) | ||||
| 	case ubanNickmask: | ||||
| 		targetString = target.nickOrMask | ||||
| 		err = client.server.klines.RemoveMask(target.nickOrMask) | ||||
| 	case ubanNick: | ||||
| 		targetString = target.nickOrMask | ||||
| 		err = client.server.accounts.Unsuspend(target.nickOrMask) | ||||
| 	} | ||||
| 	if err == nil { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Successfully removed ban on %s"), targetString)) | ||||
| 	} else { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Could not remove ban: %v"), err)) | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func ubanListHandler(client *Client, params []string, rb *ResponseBuffer) bool { | ||||
| 	allDlines := client.server.dlines.AllBans() | ||||
| 	rb.Notice(fmt.Sprintf(client.t("There are %d active IP/network ban(s) (DLINEs)"), len(allDlines))) | ||||
| 	for key, info := range allDlines { | ||||
| 		rb.Notice(formatBanForListing(client, key, info)) | ||||
| 	} | ||||
| 	rb.Notice(client.t("Some IPs may also be prevented from connecting by the connection limiter and/or throttler")) | ||||
| 
 | ||||
| 	allKlines := client.server.klines.AllBans() | ||||
| 	rb.Notice(fmt.Sprintf(client.t("There are %d active ban(s) on nick-user-host masks (KLINEs)"), len(allKlines))) | ||||
| 	for key, info := range allKlines { | ||||
| 		rb.Notice(formatBanForListing(client, key, info)) | ||||
| 	} | ||||
| 
 | ||||
| 	listAccountSuspensions(client, rb, client.server.name) | ||||
| 
 | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func ubanInfoHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool { | ||||
| 	switch target.banType { | ||||
| 	case ubanCIDR: | ||||
| 		ubanInfoCIDR(client, target, rb) | ||||
| 	case ubanNickmask: | ||||
| 		ubanInfoNickmask(client, target, rb) | ||||
| 	case ubanNick: | ||||
| 		ubanInfoNick(client, target, rb) | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| 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() | ||||
| 		if status.Exempt { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("IP %s is exempt from connection limits"), str)) | ||||
| 		} 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)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	str := target.cidr.HumanReadableString() | ||||
| 	isBanned, banInfo := client.server.dlines.CheckIP(target.cidr.IP) | ||||
| 	if isBanned { | ||||
| 		rb.Notice(formatBanForListing(client, str, banInfo)) | ||||
| 	} else { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("There is no active IP ban against %s"), str)) | ||||
| 	} | ||||
| 
 | ||||
| 	sessions, nicks := sessionsForCIDR(client.server, target.cidr, nil) | ||||
| 	if len(sessions) != 0 { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("There are %[1]d active client(s) from %[2]s, associated with %[3]d nickname(s):"), len(sessions), target.cidr.String(), len(nicks))) | ||||
| 		for _, line := range utils.BuildTokenLines(400, nicks, " ") { | ||||
| 			rb.Notice(line) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ubanInfoNickmask(client *Client, target ubanTarget, rb *ResponseBuffer) { | ||||
| 	isBanned, info := client.server.klines.ContainsMask(target.nickOrMask) | ||||
| 	if isBanned { | ||||
| 		rb.Notice(formatBanForListing(client, target.nickOrMask, info)) | ||||
| 	} else { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("No ban exists for %[1]s"), target.nickOrMask)) | ||||
| 	} | ||||
| 
 | ||||
| 	affectedCount := 0 | ||||
| 	alwaysOnCount := 0 | ||||
| 	for _, mcl := range client.server.clients.AllClients() { | ||||
| 		matches := false | ||||
| 		for _, mask := range mcl.AllNickmasks() { | ||||
| 			if target.matcher.MatchString(mask) { | ||||
| 				matches = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if matches { | ||||
| 			if mcl.AlwaysOn() { | ||||
| 				alwaysOnCount++ | ||||
| 			} else { | ||||
| 				affectedCount++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	rb.Notice(fmt.Sprintf(client.t("Adding this mask would affect %[1]d clients (an additional %[2]d clients are exempt due to always-on)"), affectedCount, alwaysOnCount)) | ||||
| } | ||||
| 
 | ||||
| func ubanInfoNick(client *Client, target ubanTarget, rb *ResponseBuffer) { | ||||
| 	mcl := client.server.clients.Get(target.nickOrMask) | ||||
| 	if mcl != nil { | ||||
| 		details := mcl.Details() | ||||
| 		if details.account == "" { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("Client %[1]s is unauthenticated and connected from %[2]s"), details.nick, client.IP().String())) | ||||
| 		} 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 := client.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())) | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("No client is currently using that nickname"))) | ||||
| 	} | ||||
| 
 | ||||
| 	account, err := client.server.accounts.LoadAccount(target.nickOrMask) | ||||
| 	if err != nil { | ||||
| 		if err == errAccountDoesNotExist { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("There is no account registered for %s"), target.nickOrMask)) | ||||
| 		} else { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("Couldn't load account: %v"), err.Error())) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	if account.Verified { | ||||
| 		if account.Suspended == nil { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("Account %[1]s is in good standing; see /NICKSERV INFO %[2]s for more details"), target.nickOrMask, target.nickOrMask)) | ||||
| 		} else { | ||||
| 			rb.Notice(fmt.Sprintf(client.t("Account %[1]s has been suspended: %[2]s"), target.nickOrMask, suspensionToString(client, *account.Suspended))) | ||||
| 		} | ||||
| 	} else { | ||||
| 		rb.Notice(fmt.Sprintf(client.t("Account %[1]s was created, but has not been verified"), target.nickOrMask)) | ||||
| 	} | ||||
| } | ||||
| @ -556,9 +556,8 @@ oper-classes: | ||||
| 
 | ||||
|         # capability names | ||||
|         capabilities: | ||||
|             - "local_kill" | ||||
|             - "local_ban" | ||||
|             - "local_unban" | ||||
|             - "kill" | ||||
|             - "ban" | ||||
|             - "nofakelag" | ||||
|             - "roleplay" | ||||
|             - "relaymsg" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Shivaram Lingamneni
						Shivaram Lingamneni