mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-15 00:19:29 +01:00
Issue #68, initial dnsbl system
This commit is contained in:
parent
744ad2ce0b
commit
5e7aceb75e
@ -82,6 +82,8 @@ type Client struct {
|
|||||||
username string
|
username string
|
||||||
vhost string
|
vhost string
|
||||||
whoisLine string
|
whoisLine string
|
||||||
|
requireSasl bool
|
||||||
|
requireSaslReason string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a client with all the appropriate info setup.
|
// 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()
|
client.languages = server.languages.Default()
|
||||||
|
|
||||||
|
// Check IP towards speified DNSBLs
|
||||||
|
server.ProcessBlacklist(client)
|
||||||
|
|
||||||
client.recomputeMaxlens()
|
client.recomputeMaxlens()
|
||||||
if isTLS {
|
if isTLS {
|
||||||
client.flags[modes.TLS] = true
|
client.flags[modes.TLS] = true
|
||||||
|
@ -90,6 +90,25 @@ type AccountRegistrationConfig struct {
|
|||||||
AllowMultiplePerConnection bool `yaml:"allow-multiple-per-connection"`
|
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
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DnsblListReply struct {
|
||||||
|
Action string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
type NickReservationMethod int
|
type NickReservationMethod int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -263,6 +282,7 @@ type Config struct {
|
|||||||
LineLen LineLenConfig `yaml:"linelen"`
|
LineLen LineLenConfig `yaml:"linelen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dnsbl DnsblConfig
|
||||||
Fakelag FakelagConfig
|
Fakelag FakelagConfig
|
||||||
|
|
||||||
Filename string
|
Filename string
|
||||||
@ -471,6 +491,26 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
newWebIRC = append(newWebIRC, webirc)
|
newWebIRC = append(newWebIRC, webirc)
|
||||||
}
|
}
|
||||||
config.Server.WebIRC = newWebIRC
|
config.Server.WebIRC = newWebIRC
|
||||||
|
|
||||||
|
for id, list := range config.Dnsbl.Lists {
|
||||||
|
var action, reason = list.Action, list.Reason
|
||||||
|
|
||||||
|
var newDnsblListReply = make(map[string]DnsblListReply)
|
||||||
|
for key, reply := range list.Reply {
|
||||||
|
if reply.Action == "" {
|
||||||
|
reply.Action = action
|
||||||
|
}
|
||||||
|
if reply.Reason == "" {
|
||||||
|
reply.Reason = reason
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, newKey := range strings.Split(key, ",") {
|
||||||
|
newDnsblListReply[newKey] = reply
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.Dnsbl.Lists[id].Reply = newDnsblListReply
|
||||||
|
}
|
||||||
|
|
||||||
// process limits
|
// process limits
|
||||||
if config.Limits.LineLen.Tags < 512 || config.Limits.LineLen.Rest < 512 {
|
if config.Limits.LineLen.Tags < 512 || config.Limits.LineLen.Rest < 512 {
|
||||||
return nil, ErrLineLengthsTooSmall
|
return nil, ErrLineLengthsTooSmall
|
||||||
|
142
irc/dnsbl.go
Normal file
142
irc/dnsbl.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/sno"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastIpOctet(addr string) string {
|
||||||
|
address := strings.Split(addr, ".")
|
||||||
|
|
||||||
|
return address[len(address)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) LookupBlacklistEntry(list *DnsblListEntry, client *Client) []string {
|
||||||
|
res, err := net.LookupHost(fmt.Sprintf("%s.%s", ReverseAddress(client.IP()), list.Host))
|
||||||
|
|
||||||
|
var entries []string
|
||||||
|
if err != nil {
|
||||||
|
server.logger.Info("dnsbl-lookup", fmt.Sprintf("DNSBL loopup failed: %s", err))
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
for _, addr := range res {
|
||||||
|
entries = append(entries, LastIpOctet(addr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendDnsblMessage(client *Client, message string) {
|
||||||
|
/*fmt.Printf(client.server.DnsblConfig().Channel)
|
||||||
|
if channel := client.server.DnsblConfig().Channel; channel != "" {
|
||||||
|
fmt.Printf(channel)
|
||||||
|
client.Send(nil, client.server.name, "PRIVMSG", channel, message)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
client.server.snomasks.Send(sno.Dnsbl, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type DnsblTypeResponse struct {
|
||||||
|
Host string
|
||||||
|
Action string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
var items = []DnsblTypeResponse{}
|
||||||
|
for _, list := range server.DnsblConfig().Lists {
|
||||||
|
response := DnsblTypeResponse{
|
||||||
|
Host: list.Host,
|
||||||
|
Action: list.Action,
|
||||||
|
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.Action, response.Reason = reply.Action, reply.Reason
|
||||||
|
}
|
||||||
|
items = append(items, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort responses so that require-sasl blocks come first. Otherwise A>B (allow>block, allow>notify, block>notify)
|
||||||
|
// so that responses come in this order:
|
||||||
|
// - require-sasl
|
||||||
|
// - allow
|
||||||
|
// - block
|
||||||
|
// - notify
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
if items[i].Action == "require-sasl" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return items[i].Action > items[j].Action
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(items) > 0 {
|
||||||
|
item := items[0]
|
||||||
|
switch item.Action {
|
||||||
|
case "require-sasl":
|
||||||
|
sendDnsblMessage(client, fmt.Sprintf("Connecting client %s matched %s, requiring SASL to proceed", client.IP(), item.Host))
|
||||||
|
client.SetRequireSasl(true, item.Reason)
|
||||||
|
|
||||||
|
case "block":
|
||||||
|
sendDnsblMessage(client, fmt.Sprintf("Connecting client %s matched %s - killing", client.IP(), item.Host))
|
||||||
|
client.Quit(strings.Replace(item.Reason, "{ip}", client.IPString(), -1))
|
||||||
|
|
||||||
|
case "notify":
|
||||||
|
sendDnsblMessage(client, fmt.Sprintf("Connecting client %s matched %s", client.IP(), item.Host))
|
||||||
|
|
||||||
|
case "allow":
|
||||||
|
sendDnsblMessage(client, 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() == "" {
|
||||||
|
sendDnsblMessage(client, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
sendDnsblMessage(client, fmt.Sprintf("Connecting client %s authenticated through SASL - allowing", client.IP()))
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
@ -4,9 +4,10 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/isupport"
|
"github.com/oragono/oragono/irc/isupport"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (server *Server) MaxSendQBytes() int {
|
func (server *Server) MaxSendQBytes() int {
|
||||||
@ -74,6 +75,15 @@ func (server *Server) AccountConfig() *AccountConfig {
|
|||||||
return &server.config.Accounts
|
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 {
|
func (server *Server) FakelagConfig() *FakelagConfig {
|
||||||
server.configurableStateMutex.RLock()
|
server.configurableStateMutex.RLock()
|
||||||
defer server.configurableStateMutex.RUnlock()
|
defer server.configurableStateMutex.RUnlock()
|
||||||
@ -175,6 +185,19 @@ func (client *Client) SetAuthorized(authorized bool) {
|
|||||||
client.authorized = authorized
|
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 {
|
func (client *Client) PreregNick() string {
|
||||||
client.stateMutex.RLock()
|
client.stateMutex.RLock()
|
||||||
defer client.stateMutex.RUnlock()
|
defer client.stateMutex.RUnlock()
|
||||||
|
@ -1785,7 +1785,7 @@ 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))
|
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
|
// increase oper count
|
||||||
server.stats.ChangeOperators(1)
|
//server.stats.ChangeOperators(1)
|
||||||
|
|
||||||
// client may now be unthrottled by the fakelag system
|
// client may now be unthrottled by the fakelag system
|
||||||
client.resetFakelag()
|
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:
|
case modes.Bot, modes.Invisible, modes.WallOps, modes.UserRoleplaying, modes.Operator, modes.LocalOperator, modes.RegisteredOnly:
|
||||||
switch change.Op {
|
switch change.Op {
|
||||||
case modes.Add:
|
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) {
|
if !force && (change.Mode == modes.Operator || change.Mode == modes.LocalOperator) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -446,6 +446,11 @@ func (server *Server) tryRegister(c *Client) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if connectionRequiresSasl(c) {
|
||||||
|
c.destroy(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// client MUST send PASS (or AUTHENTICATE, if skip-server-password is set)
|
// client MUST send PASS (or AUTHENTICATE, if skip-server-password is set)
|
||||||
// before completing the other registration commands
|
// before completing the other registration commands
|
||||||
if !c.Authorized() {
|
if !c.Authorized() {
|
||||||
|
@ -19,6 +19,7 @@ const (
|
|||||||
Stats Mask = 't'
|
Stats Mask = 't'
|
||||||
LocalAccounts Mask = 'u'
|
LocalAccounts Mask = 'u'
|
||||||
LocalXline Mask = 'x'
|
LocalXline Mask = 'x'
|
||||||
|
Dnsbl Mask = 'S'
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -34,6 +35,7 @@ var (
|
|||||||
Stats: "STATS",
|
Stats: "STATS",
|
||||||
LocalAccounts: "ACCOUNT",
|
LocalAccounts: "ACCOUNT",
|
||||||
LocalXline: "XLINE",
|
LocalXline: "XLINE",
|
||||||
|
Dnsbl: "DNSBL",
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidMasks contains the snomasks that we support.
|
// ValidMasks contains the snomasks that we support.
|
||||||
@ -48,5 +50,6 @@ var (
|
|||||||
Stats: true,
|
Stats: true,
|
||||||
LocalAccounts: true,
|
LocalAccounts: true,
|
||||||
LocalXline: true,
|
LocalXline: true,
|
||||||
|
Dnsbl: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user