Initial DLINE changes

This commit is contained in:
Daniel Oaks 2016-11-04 12:42:58 +10:00
parent 6beaeff9c7
commit 8c797d0f76
5 changed files with 224 additions and 10 deletions

View File

@ -10,6 +10,7 @@ New release of Oragono!
### Security
### Added
* Added ability to `DLINE` IP addresses ad networks, to allow for fast blocking.
### Changed

View File

@ -76,6 +76,11 @@ var Commands = map[string]Command{
handler: debugHandler,
minParams: 1,
},
"DLINE": {
handler: dlineHandler,
minParams: 1,
oper: true,
},
"HELP": {
handler: helpHandler,
minParams: 0,
@ -200,6 +205,11 @@ var Commands = map[string]Command{
handler: topicHandler,
minParams: 1,
},
"UNDLINE": {
handler: undlineHandler,
minParams: 1,
oper: true,
},
"USER": {
handler: userHandler,
usablePreReg: true,

151
irc/dline.go Normal file
View File

@ -0,0 +1,151 @@
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import "net"
import "time"
// IPRestrictTime contains the expiration info about the given IP.
type IPRestrictTime struct {
// Expires is when this block expires.
Expires time.Time
// Length is how long this block lasts for.
Length time.Duration
}
// IsExpired returns true if the time has expired.
func (iptime *IPRestrictTime) IsExpired() bool {
return iptime.Expires.Before(time.Now())
}
// IPBanInfo holds info about an IP/net ban.
type IPBanInfo struct {
// Reason is the ban reason.
Reason string
// OperReason is an oper ban reason.
OperReason string
// Time holds details about the duration, if it exists.
Time *IPRestrictTime
}
// dLineAddr contains the address itself and expiration time for a given network.
type dLineAddr struct {
// Address is the address that is blocked.
Address net.IP
// Info contains information on the ban.
Info IPBanInfo
}
// dLineNet contains the net itself and expiration time for a given network.
type dLineNet struct {
// Network is the network that is blocked.
Network net.IPNet
// Info contains information on the ban.
Info IPBanInfo
}
// DLineManager manages and dlines.
type DLineManager struct {
// addresses that are dlined
addresses map[string]*dLineAddr
// networks that are dlined
networks map[string]*dLineNet
}
// NewDLineManager returns a new DLineManager.
func NewDLineManager() *DLineManager {
dm := DLineManager{
addresses: make(map[string]*dLineAddr),
networks: make(map[string]*dLineNet),
}
return &dm
}
// AddNetwork adds a network to the blocked list.
func (dm *DLineManager) AddNetwork(network net.IPNet, length *IPRestrictTime) {
netString := network.String()
dln := dLineNet{
Network: network,
Info: IPBanInfo{
Time: length,
Reason: "",
OperReason: "",
},
}
dm.networks[netString] = &dln
}
// RemoveNetwork removes a network from the blocked list.
func (dm *DLineManager) RemoveNetwork(network net.IPNet) {
netString := network.String()
delete(dm.networks, netString)
}
// AddIP adds an IP address to the blocked list.
func (dm *DLineManager) AddIP(addr net.IP, length *IPRestrictTime) {
addrString := addr.String()
dla := dLineAddr{
Address: addr,
Info: IPBanInfo{
Time: length,
Reason: "",
OperReason: "",
},
}
dm.addresses[addrString] = &dla
}
// RemoveIP removes an IP from the blocked list.
func (dm *DLineManager) RemoveIP(addr net.IP) {
addrString := addr.String()
delete(dm.addresses, addrString)
}
// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info *IPBanInfo) {
// check IP addr
addrString := addr.String()
addrInfo := dm.addresses[addrString]
if addrInfo != nil {
if addrInfo.Info.Time != nil {
if addrInfo.Info.Time.IsExpired() {
// ban on IP has expired, remove it from our blocked list
dm.RemoveIP(addr)
} else {
return true, &addrInfo.Info
}
} else {
return true, nil
}
}
// check networks
var netsToRemove []net.IPNet
for _, netInfo := range dm.networks {
if !netInfo.Network.Contains(addr) {
continue
}
if netInfo.Info.Time != nil {
if netInfo.Info.Time.IsExpired() {
// ban on network has expired, remove it from our blocked list
netsToRemove = append(netsToRemove, netInfo.Network)
} else {
return true, &addrInfo.Info
}
} else {
return true, nil
}
}
// remove expired networks
for _, expiredNet := range netsToRemove {
dm.RemoveNetwork(expiredNet)
}
// no matches!
return false, nil
}

View File

@ -36,7 +36,8 @@ Oragono supports the following channel modes:
+i | Invite-only mode, only invited clients can join the channel.
+m | Moderated mode, only privileged clients can talk on the channel.
+n | No-outside-messages mode, only users that are on the channel can send the channel messages.
+n | No-outside-messages mode, only users that are on the channel can send
| messages to it.
+t | Only channel opers can modify the topic.
+s | Secret mode, channel won't show up in /LIST or whois replies.
@ -95,6 +96,26 @@ Prints debug information about the IRCd. <option> can be one of:
* STARTCPUPROFILE: Starts the CPU profiler.
* STOPCPUPROFILE: Stops the CPU profiler.
* PROFILEHEAP: Writes out the CPU profiler info.`,
},
"dline": {
oper: true,
text: `DLINE [duration] <ip>/<net> [reason [| oper reason]]
Bans an IP address or network from connecting to the server. If the duration is
given then only for that long. The reason is shown to the user themselves, but
everyone else will see a standard message. The oper reason is shown to
operators getting info about the DLINEs that exist.
Bans are saved across subsequent launches of the server.
[duration] can be of the following forms:
10h 8m 13s
<net> is specified in typical CIDR notation. For example:
127.0.0.1/8
8.8.8.8/24
[reason] and [oper reason], if they exist, are separated by a vertical bar (|).`,
},
"help": {
text: `HELP <argument>
@ -250,7 +271,7 @@ Indicates that you're leaving the server, and shows everyone the given reason.`,
REG VERIFY <accountname> <auth_code>
Used in account registration. See the relevant specs for more info:
https://github.com/DanielOaks/ircv3-specifications/blob/register-and-verify/extensions/reg-core-3.3.md`,
http://oragono.io/specs.html`,
},
"rehash": {
oper: true,
@ -268,6 +289,16 @@ 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.`,
},
"undline": {
oper: true,
text: `UNDLINE <ip>/<net>
Removes an existing ban on an IP address or a network.
<net> is specified in typical CIDR notation. For example:
127.0.0.1/8
8.8.8.8/24`,
},
"user": {
text: `USER <username> 0 * <realname>
@ -316,8 +347,7 @@ Returns historical information on the last user with the given nickname.`,
Oragono supports an experimental unicode casemapping designed for extended
Unicode support. This casemapping is based off RFC 7700 and the draft rfc7700
casemapping spec here:
https://github.com/DanielOaks/ircv3-specifications/blob/master%2Brfc7700/documentation/rfc7700.md`,
casemapping spec here: http://oragono.io/specs.html`,
},
"prefix": {
text: `RPL_ISUPPORT PREFIX

View File

@ -29,6 +29,9 @@ var (
// cached because this may be used lots
tooManyClientsMsg = ircmsg.MakeMessage(nil, "", "ERROR", "Too many clients from your IP or network")
tooManyClientsBytes, _ = tooManyClientsMsg.Line()
bannedFromServerMsg = ircmsg.MakeMessage(nil, "", "ERROR", "You are banned from this server (%s)")
bannedFromServerBytes, _ = bannedFromServerMsg.Line()
)
// Limits holds the maximum limits for various things such as topic lengths
@ -78,6 +81,7 @@ type Server struct {
connectionLimitsMutex sync.Mutex // used when affecting the connection limiter, to make sure rehashing doesn't make things go out-of-whack
ctime time.Time
currentOpers map[*Client]bool
dlines *DLineManager
idle chan *Client
isupport *ISupportList
limits Limits
@ -190,6 +194,9 @@ func NewServer(configFilename string, config *Config) *Server {
}
server.store = *db
// load dlines
server.loadDLines()
// load password manager
err = server.store.View(func(tx *buntdb.Tx) error {
saltString, err := tx.Get(keySalt)
@ -342,18 +349,33 @@ func (server *Server) Run() {
// check connection limits
ipaddr := net.ParseIP(IPString(conn.Conn.RemoteAddr()))
if ipaddr != nil {
// check DLINEs
isBanned, info := server.dlines.CheckIP(ipaddr)
if isBanned {
banMessage := fmt.Sprintf(bannedFromServerBytes, info.Reason)
if info.Time != nil {
banMessage += fmt.Sprintf(" [%s]", info.Time.Length.String())
}
conn.Conn.Write([]byte(banMessage))
conn.Conn.Close()
continue
}
// check connection limits
server.connectionLimitsMutex.Lock()
err := server.connectionLimits.AddClient(ipaddr, false)
server.connectionLimitsMutex.Unlock()
if err == nil {
go NewClient(server, conn.Conn, conn.IsTLS)
if err != nil {
// too many connections from one client, tell the client and close the connection
// 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
conn.Conn.Write([]byte(tooManyClientsBytes))
conn.Conn.Close()
continue
}
go NewClient(server, conn.Conn, conn.IsTLS)
continue
}
// too many connections from one client, tell the client and close the connection
// 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
conn.Conn.Write([]byte(tooManyClientsBytes))
conn.Conn.Close()
case client := <-server.idle:
client.Idle()