forked from External/ergo
Compare commits
3 commits
master
...
devel+dnsb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9756ce02ba | ||
|
|
6a9fa35d35 | ||
|
|
5e7aceb75e |
10 changed files with 320 additions and 4 deletions
|
|
@ -82,6 +82,8 @@ type Client struct {
|
|||
username string
|
||||
vhost string
|
||||
whoisLine string
|
||||
requireSasl bool
|
||||
requireSaslReason string
|
||||
}
|
||||
|
||||
// NewClient returns a client with all the appropriate info setup.
|
||||
|
|
@ -108,6 +110,9 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||
}
|
||||
client.languages = server.languages.Default()
|
||||
|
||||
// Check IP towards speified DNSBLs
|
||||
server.ProcessBlacklist(client)
|
||||
|
||||
client.recomputeMaxlens()
|
||||
if isTLS {
|
||||
client.flags[modes.TLS] = true
|
||||
|
|
|
|||
|
|
@ -90,6 +90,42 @@ type AccountRegistrationConfig struct {
|
|||
AllowMultiplePerConnection bool `yaml:"allow-multiple-per-connection"`
|
||||
}
|
||||
|
||||
type DnsblConfig struct {
|
||||
Enabled bool
|
||||
Channel string
|
||||
Lists []DnsblListEntry `yaml:"lists"`
|
||||
}
|
||||
|
||||
type DnsblListEntry struct {
|
||||
Host string
|
||||
Types []string
|
||||
Reply map[string]DnsblListReply
|
||||
Action string
|
||||
ActionType DnsblActionType
|
||||
Reason string
|
||||
}
|
||||
|
||||
type DnsblListReply struct {
|
||||
Action string
|
||||
ActionType DnsblActionType
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (conf *Config) DnsblTypes(action string) (DnsblActionType, error) {
|
||||
actions := map[string]DnsblActionType{
|
||||
"require-sasl": DnsblRequireSaslReply,
|
||||
"allow": DnsblAllowReply,
|
||||
"block": DnsblBlockReply,
|
||||
"notify": DnsblNotifyReply,
|
||||
}
|
||||
|
||||
if value, exists := actions[action]; exists {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return DnsblUnknownReply, errors.New(fmt.Sprintf("Unknown DNSBL action type: %s", action))
|
||||
}
|
||||
|
||||
type NickReservationMethod int
|
||||
|
||||
const (
|
||||
|
|
@ -263,6 +299,7 @@ type Config struct {
|
|||
LineLen LineLenConfig `yaml:"linelen"`
|
||||
}
|
||||
|
||||
Dnsbl DnsblConfig
|
||||
Fakelag FakelagConfig
|
||||
|
||||
Filename string
|
||||
|
|
@ -471,6 +508,31 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||
newWebIRC = append(newWebIRC, webirc)
|
||||
}
|
||||
config.Server.WebIRC = newWebIRC
|
||||
|
||||
for id, list := range config.Dnsbl.Lists {
|
||||
actionType, err := config.DnsblTypes(list.Action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reason := list.Reason
|
||||
|
||||
var newDnsblListReply = make(map[string]DnsblListReply)
|
||||
for key, reply := range list.Reply {
|
||||
if reply.Action == "" {
|
||||
reply.ActionType = actionType
|
||||
}
|
||||
if reply.Reason == "" {
|
||||
reply.Reason = reason
|
||||
}
|
||||
|
||||
for _, newKey := range strings.Split(key, ",") {
|
||||
newDnsblListReply[newKey] = reply
|
||||
}
|
||||
}
|
||||
config.Dnsbl.Lists[id].ActionType = actionType
|
||||
config.Dnsbl.Lists[id].Reply = newDnsblListReply
|
||||
}
|
||||
|
||||
// process limits
|
||||
if config.Limits.LineLen.Tags < 512 || config.Limits.LineLen.Rest < 512 {
|
||||
return nil, ErrLineLengthsTooSmall
|
||||
|
|
|
|||
128
irc/dnsbl.go
Normal file
128
irc/dnsbl.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/oragono/oragono/irc/sno"
|
||||
"github.com/oragono/oragono/irc/utils"
|
||||
)
|
||||
|
||||
// Constants
|
||||
type DnsblActionType uint
|
||||
|
||||
const (
|
||||
DnsblRequireSaslReply DnsblActionType = iota
|
||||
DnsblAllowReply
|
||||
DnsblBlockReply
|
||||
DnsblNotifyReply
|
||||
DnsblUnknownReply
|
||||
)
|
||||
|
||||
// LookupBlacklistEntry performs a lookup on the dnsbl on the client IP
|
||||
func (server *Server) LookupBlacklistEntry(list *DnsblListEntry, client *Client) []string {
|
||||
res, err := net.LookupHost(fmt.Sprintf("%s.%s", utils.ReverseAddress(client.IP()), list.Host))
|
||||
|
||||
var entries []string
|
||||
if err != nil {
|
||||
// An error may indicate that the A record was not found
|
||||
return entries
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
for _, addr := range res {
|
||||
octet := strings.Split(addr, ".")
|
||||
if len(octet) > 0 {
|
||||
entries = append(entries, octet[len(octet)-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// ProcessBlacklist does
|
||||
func (server *Server) ProcessBlacklist(client *Client) {
|
||||
|
||||
if !server.DnsblConfig().Enabled || len(server.DnsblConfig().Lists) == 0 {
|
||||
// do nothing if dnsbl is disabled, empty lists is treated as if dnsbl was disabled
|
||||
return
|
||||
}
|
||||
|
||||
lists := server.DnsblConfig().Lists
|
||||
|
||||
type DnsblTypeResponse struct {
|
||||
Host string
|
||||
ActionType DnsblActionType
|
||||
Reason string
|
||||
}
|
||||
var items = []DnsblTypeResponse{}
|
||||
for _, list := range lists {
|
||||
response := DnsblTypeResponse{
|
||||
Host: list.Host,
|
||||
ActionType: list.ActionType,
|
||||
Reason: list.Reason,
|
||||
}
|
||||
// update action/reason if matched with new ...
|
||||
for _, entry := range server.LookupBlacklistEntry(&list, client) {
|
||||
if reply, exists := list.Reply[entry]; exists {
|
||||
response.ActionType, response.Reason = list.ActionType, reply.Reason
|
||||
}
|
||||
items = append(items, response)
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts in the following order: require-sasl, allow, block, notify
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].ActionType > items[j].ActionType
|
||||
})
|
||||
|
||||
if len(items) > 0 {
|
||||
item := items[0]
|
||||
switch item.ActionType {
|
||||
case DnsblRequireSaslReply:
|
||||
dnsblSendServiceMessage(server, fmt.Sprintf("Connecting client %s matched %s, requiring SASL to proceed", client.IP(), item.Host))
|
||||
client.SetRequireSasl(true, item.Reason)
|
||||
|
||||
case DnsblBlockReply:
|
||||
dnsblSendServiceMessage(server, fmt.Sprintf("Connecting client %s matched %s - killing", client.IP(), item.Host))
|
||||
client.Quit(strings.Replace(item.Reason, "{ip}", client.IPString(), -1))
|
||||
|
||||
case DnsblNotifyReply:
|
||||
dnsblSendServiceMessage(server, fmt.Sprintf("Connecting client %s matched %s", client.IP(), item.Host))
|
||||
|
||||
case DnsblAllowReply:
|
||||
dnsblSendServiceMessage(server, fmt.Sprintf("Allowing host %s [%s]", client.IP(), item.Host))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectionRequiresSasl(client *Client) bool {
|
||||
sasl, reason := client.RequireSasl()
|
||||
|
||||
if !sasl {
|
||||
return false
|
||||
}
|
||||
|
||||
if client.Account() == "" {
|
||||
dnsblSendServiceMessage(client.server, fmt.Sprintf("Connecting client %s and did not authenticate through SASL - blocking connection", client.IP()))
|
||||
client.Quit(strings.Replace(reason, "{ip}", client.IPString(), -1))
|
||||
return true
|
||||
}
|
||||
|
||||
dnsblSendServiceMessage(client.server, fmt.Sprintf("Connecting client %s authenticated through SASL - allowing", client.IP()))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsblSendServiceMessage(server *Server, message string) {
|
||||
channel := server.DnsblConfig().Channel
|
||||
if channel != "" {
|
||||
server.serviceNotifyChannel(server.name, channel, message)
|
||||
}
|
||||
server.snomasks.Send(sno.Dnsbl, message)
|
||||
}
|
||||
|
|
@ -4,9 +4,10 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/oragono/oragono/irc/isupport"
|
||||
"github.com/oragono/oragono/irc/modes"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func (server *Server) MaxSendQBytes() int {
|
||||
|
|
@ -74,6 +75,15 @@ func (server *Server) AccountConfig() *AccountConfig {
|
|||
return &server.config.Accounts
|
||||
}
|
||||
|
||||
func (server *Server) DnsblConfig() *DnsblConfig {
|
||||
server.configurableStateMutex.RLock()
|
||||
defer server.configurableStateMutex.RUnlock()
|
||||
if server.config == nil {
|
||||
return nil
|
||||
}
|
||||
return &server.config.Dnsbl
|
||||
}
|
||||
|
||||
func (server *Server) FakelagConfig() *FakelagConfig {
|
||||
server.configurableStateMutex.RLock()
|
||||
defer server.configurableStateMutex.RUnlock()
|
||||
|
|
@ -175,6 +185,19 @@ func (client *Client) SetAuthorized(authorized bool) {
|
|||
client.authorized = authorized
|
||||
}
|
||||
|
||||
func (client *Client) RequireSasl() (bool, string) {
|
||||
client.stateMutex.RLock()
|
||||
defer client.stateMutex.RUnlock()
|
||||
return client.requireSasl, client.requireSaslReason
|
||||
}
|
||||
|
||||
func (client *Client) SetRequireSasl(required bool, reason string) {
|
||||
client.stateMutex.Lock()
|
||||
defer client.stateMutex.Unlock()
|
||||
client.requireSasl = required
|
||||
client.requireSaslReason = reason
|
||||
}
|
||||
|
||||
func (client *Client) PreregNick() string {
|
||||
client.stateMutex.RLock()
|
||||
defer client.stateMutex.RUnlock()
|
||||
|
|
|
|||
|
|
@ -1784,9 +1784,6 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||
|
||||
server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]"), client.nickMaskString, client.operName))
|
||||
|
||||
// increase oper count
|
||||
server.stats.ChangeOperators(1)
|
||||
|
||||
// client may now be unthrottled by the fakelag system
|
||||
client.resetFakelag()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ func (client *Client) applyUserModeChanges(force bool, changes modes.ModeChanges
|
|||
case modes.Bot, modes.Invisible, modes.WallOps, modes.UserRoleplaying, modes.Operator, modes.LocalOperator, modes.RegisteredOnly:
|
||||
switch change.Op {
|
||||
case modes.Add:
|
||||
if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
|
||||
client.server.stats.ChangeOperators(1)
|
||||
}
|
||||
|
||||
if !force && (change.Mode == modes.Operator || change.Mode == modes.LocalOperator) {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -462,6 +462,12 @@ func (server *Server) tryRegister(c *Client) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check if connection requires SASL
|
||||
if ConnectionRequiresSasl(c) {
|
||||
c.destroy(false)
|
||||
return
|
||||
}
|
||||
|
||||
// check KLINEs
|
||||
isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
|
||||
if isBanned {
|
||||
|
|
@ -1224,6 +1230,18 @@ func (target *Client) RplList(channel *Channel, rb *ResponseBuffer) {
|
|||
rb.Add(nil, target.server.name, RPL_LIST, target.nick, channel.name, strconv.Itoa(memberCount), channel.topic)
|
||||
}
|
||||
|
||||
// serviceNotifyChannel sends messages to a channel as a pseudo client
|
||||
func (server *Server) serviceNotifyChannel(pseudoClient string, channelName string, message string) {
|
||||
channel := server.channels.Get(channelName)
|
||||
if channel == nil {
|
||||
return
|
||||
}
|
||||
channelName = channel.Name()
|
||||
for _, client := range channel.Members() {
|
||||
client.Send(nil, pseudoClient, "PRIVMSG", channelName, message)
|
||||
}
|
||||
}
|
||||
|
||||
// ResumeDetails are the details that we use to resume connections.
|
||||
type ResumeDetails struct {
|
||||
OldNick string
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const (
|
|||
Stats Mask = 't'
|
||||
LocalAccounts Mask = 'u'
|
||||
LocalXline Mask = 'x'
|
||||
Dnsbl Mask = 'S'
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -34,6 +35,7 @@ var (
|
|||
Stats: "STATS",
|
||||
LocalAccounts: "ACCOUNT",
|
||||
LocalXline: "XLINE",
|
||||
Dnsbl: "DNSBL",
|
||||
}
|
||||
|
||||
// ValidMasks contains the snomasks that we support.
|
||||
|
|
@ -48,5 +50,6 @@ var (
|
|||
Stats: true,
|
||||
LocalAccounts: true,
|
||||
LocalXline: true,
|
||||
Dnsbl: true,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -94,3 +94,20 @@ func IsHostname(name string) bool {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
// ReverseAddress returns IPv4 addresses reversed
|
||||
func ReverseAddress(ip net.IP) string {
|
||||
// This is a IPv4 address
|
||||
if ip.To4() != nil {
|
||||
address := strings.Split(ip.String(), ".")
|
||||
|
||||
for i, j := 0, len(address)-1; i < j; i, j = i+1, j-1 {
|
||||
address[i], address[j] = address[j], address[i]
|
||||
}
|
||||
|
||||
return strings.Join(address, ".")
|
||||
}
|
||||
|
||||
// fallback to returning the String of IP if it is not an IPv4 address
|
||||
return ip.String()
|
||||
}
|
||||
|
|
|
|||
59
oragono.yaml
59
oragono.yaml
|
|
@ -407,3 +407,62 @@ fakelag:
|
|||
# client status resets to the default state if they go this long without
|
||||
# sending any commands:
|
||||
cooldown: 2s
|
||||
# dnsbls are DNS block lists. they're used to automatically deny or restrict clients who
|
||||
# have been deemed bad
|
||||
dnsbl:
|
||||
# whether to check DNS block lists
|
||||
enabled: false
|
||||
|
||||
# optional channel to send rejections to if the 'notify' action is specified in any lists
|
||||
#channel: "#dnsbl"
|
||||
|
||||
# the specific dnsbls to check for every client entering the network
|
||||
lists:
|
||||
-
|
||||
# host - specific hostname to use
|
||||
host: "dnsbl.dronebl.org"
|
||||
|
||||
# which types of hosts to look up on this dnsbl. we support:
|
||||
# - ipv4: D.C.B.A.hostname
|
||||
# - ipv6: n.i.b.b.l.e.s.hostname (see explanation here: https://www.dan.me.uk/dnsbl )
|
||||
types:
|
||||
- ipv4
|
||||
- ipv6
|
||||
|
||||
# action to take if the client matches this dnsbl:
|
||||
# - allow - let the client access the network
|
||||
# - block - block the client from accessing the network, with the given message
|
||||
# - notify - send a notification to opers in-channel (if configured) and via the 'S' snomask
|
||||
# - require-sasl - require the client to login with SASL, kill them if they don't
|
||||
action: block
|
||||
|
||||
# reason that's shown if they're unable to access the network because of this rbl.
|
||||
# we support the following variables:
|
||||
# - "{ip}" - their IP address
|
||||
reason: "Your IP ({ip}) is listed in DroneBL. For assistance, see http://dronebl.org/lookup?ip={ip}"
|
||||
|
||||
# specific replies to take action on. these are based on the last octet of the return IP.
|
||||
# for example, "24" or "13,54,24" would both match a result of "127.0.0.24" from the rbl.
|
||||
reply:
|
||||
# 8/9/10/11 are all proxies
|
||||
"8,9,10,11":
|
||||
action: require-sasl
|
||||
reason: "You need to enable SASL to access this network. For assistance, see http://dronebl.org/lookup?ip={ip}"
|
||||
# 98 is an example reply type that's not real
|
||||
98:
|
||||
action: allow
|
||||
|
||||
-
|
||||
host: "rbl.efnetrbl.org"
|
||||
types:
|
||||
- ipv4
|
||||
action: block
|
||||
reason: "Your IP ({ip}) is listed in the EFnet RBL. For assistance, see http://efnetrbl.org/?i={ip}"
|
||||
|
||||
-
|
||||
host: "torexit.dan.me.uk"
|
||||
types:
|
||||
- ipv4
|
||||
- ipv6
|
||||
action: require-sasl
|
||||
reason: "You need to enable SASL to access this network while using TOR"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue