From 5e7aceb75ed001abe8387d7283a40881f4b8e590 Mon Sep 17 00:00:00 2001 From: moocow Date: Mon, 23 Apr 2018 13:16:20 +0200 Subject: [PATCH 1/3] Issue #68, initial dnsbl system --- irc/client.go | 5 ++ irc/config.go | 40 ++++++++++++ irc/dnsbl.go | 142 +++++++++++++++++++++++++++++++++++++++++++ irc/getters.go | 25 +++++++- irc/handlers.go | 2 +- irc/modes.go | 4 ++ irc/server.go | 5 ++ irc/sno/constants.go | 3 + 8 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 irc/dnsbl.go diff --git a/irc/client.go b/irc/client.go index 3be6a75b..63a33379 100644 --- a/irc/client.go +++ b/irc/client.go @@ -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 diff --git a/irc/config.go b/irc/config.go index ada0a578..bc28b1ff 100644 --- a/irc/config.go +++ b/irc/config.go @@ -90,6 +90,25 @@ 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 + Reason string +} + +type DnsblListReply struct { + Action string + Reason string +} + type NickReservationMethod int const ( @@ -263,6 +282,7 @@ type Config struct { LineLen LineLenConfig `yaml:"linelen"` } + Dnsbl DnsblConfig Fakelag FakelagConfig Filename string @@ -471,6 +491,26 @@ func LoadConfig(filename string) (config *Config, err error) { newWebIRC = append(newWebIRC, webirc) } 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 if config.Limits.LineLen.Tags < 512 || config.Limits.LineLen.Rest < 512 { return nil, ErrLineLengthsTooSmall diff --git a/irc/dnsbl.go b/irc/dnsbl.go new file mode 100644 index 00000000..2f3d7632 --- /dev/null +++ b/irc/dnsbl.go @@ -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 +} diff --git a/irc/getters.go b/irc/getters.go index 9d1c6498..c8e69266 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -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() diff --git a/irc/handlers.go b/irc/handlers.go index ac753e80..7b0e1cf4 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -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)) // increase oper count - server.stats.ChangeOperators(1) + //server.stats.ChangeOperators(1) // client may now be unthrottled by the fakelag system client.resetFakelag() diff --git a/irc/modes.go b/irc/modes.go index d8cc95b8..fa11ace7 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -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 } diff --git a/irc/server.go b/irc/server.go index 1fa9dfb4..99629ab6 100644 --- a/irc/server.go +++ b/irc/server.go @@ -446,6 +446,11 @@ func (server *Server) tryRegister(c *Client) { return } + if connectionRequiresSasl(c) { + c.destroy(false) + return + } + // client MUST send PASS (or AUTHENTICATE, if skip-server-password is set) // before completing the other registration commands if !c.Authorized() { diff --git a/irc/sno/constants.go b/irc/sno/constants.go index 5449962e..98456acf 100644 --- a/irc/sno/constants.go +++ b/irc/sno/constants.go @@ -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, } ) From 6a9fa35d35fa5ce949edbbaac46fdf391a2a603c Mon Sep 17 00:00:00 2001 From: moocow Date: Tue, 24 Apr 2018 13:47:35 +0200 Subject: [PATCH 2/3] enum types simplifying sort, config fails on wrong action types --- irc/config.go | 40 ++++++++++++++----- irc/dnsbl.go | 101 ++++++++++++++++++++++++++---------------------- irc/handlers.go | 3 -- irc/server.go | 11 +++--- oragono.yaml | 59 ++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 63 deletions(-) diff --git a/irc/config.go b/irc/config.go index bc28b1ff..443cbb80 100644 --- a/irc/config.go +++ b/irc/config.go @@ -97,16 +97,33 @@ type DnsblConfig struct { } type DnsblListEntry struct { - Host string - Types []string - Reply map[string]DnsblListReply - Action string - Reason string + Host string + Types []string + Reply map[string]DnsblListReply + Action string + ActionType uint + Reason string } type DnsblListReply struct { - Action string - Reason string + Action string + ActionType uint + Reason string +} + +func (conf *Config) DnsblTypes(action string) (uint, error) { + actions := map[string]uint{ + "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 @@ -493,12 +510,16 @@ func LoadConfig(filename string) (config *Config, err error) { config.Server.WebIRC = newWebIRC for id, list := range config.Dnsbl.Lists { - var action, reason = list.Action, list.Reason + 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.Action = action + reply.ActionType = actionType } if reply.Reason == "" { reply.Reason = reason @@ -508,6 +529,7 @@ func LoadConfig(filename string) (config *Config, err error) { newDnsblListReply[newKey] = reply } } + config.Dnsbl.Lists[id].ActionType = actionType config.Dnsbl.Lists[id].Reply = newDnsblListReply } diff --git a/irc/dnsbl.go b/irc/dnsbl.go index 2f3d7632..9dab072a 100644 --- a/irc/dnsbl.go +++ b/irc/dnsbl.go @@ -9,6 +9,16 @@ import ( "github.com/oragono/oragono/irc/sno" ) +// Constants +const ( + DnsblRequireSaslReply uint = iota + DnsblAllowReply + DnsblBlockReply + DnsblNotifyReply + DnsblUnknownReply +) + +// ReverseAddress returns IPv4 addresses reversed func ReverseAddress(ip net.IP) string { // This is a IPv4 address if ip.To4() != nil { @@ -25,12 +35,7 @@ func ReverseAddress(ip net.IP) string { return ip.String() } -func LastIpOctet(addr string) string { - address := strings.Split(addr, ".") - - return address[len(address)-1] -} - +// 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", ReverseAddress(client.IP()), list.Host)) @@ -42,23 +47,16 @@ func (server *Server) LookupBlacklistEntry(list *DnsblListEntry, client *Client) if len(res) > 0 { for _, addr := range res { - entries = append(entries, LastIpOctet(addr)) + octet := strings.Split(addr, ".") + if len(octet) > 0 { + entries = append(entries, octet[len(octet)-1]) + } } } 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) { @@ -67,56 +65,51 @@ func (server *Server) ProcessBlacklist(client *Client) { return } + channel := server.DnsblConfig().Channel + lists := server.DnsblConfig().Lists + type DnsblTypeResponse struct { - Host string - Action string - Reason string + Host string + ActionType uint + Reason string } var items = []DnsblTypeResponse{} - for _, list := range server.DnsblConfig().Lists { + for _, list := range lists { response := DnsblTypeResponse{ - Host: list.Host, - Action: list.Action, - Reason: list.Reason, + 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.Action, response.Reason = reply.Action, reply.Reason + response.ActionType, response.Reason = list.ActionType, 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 + // Sorts in the following 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 + return items[i].ActionType > items[j].ActionType }) 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)) + switch item.ActionType { + case DnsblRequireSaslReply: + client.sendServerMessage("", channel, sno.Dnsbl, 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)) + case DnsblBlockReply: + client.sendServerMessage("", channel, sno.Dnsbl, 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 DnsblNotifyReply: + client.sendServerMessage("", channel, sno.Dnsbl, 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)) + case DnsblAllowReply: + client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Allowing host %s [%s]", client.IP(), item.Host)) } } @@ -130,13 +123,29 @@ func connectionRequiresSasl(client *Client) bool { return false } + channel := client.server.DnsblConfig().Channel + if client.Account() == "" { - sendDnsblMessage(client, fmt.Sprintf("Connecting client %s and did not authenticate through SASL - blocking connection", client.IP())) + //client.sendServerMessage("", channel, sno.Dnsbl, 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())) + client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Connecting client %s authenticated through SASL - allowing", client.IP())) return false } + +func (client *Client) sendServerMessage(pseudo string, channel string, mask sno.Mask, message string) { + /* + This causes an out of bounds error - possibly in client.Send() - investigate further + if pseudo == "" { + pseudo = client.server.name + } + + if channel != "" { + client.Send(nil, pseudo, "PRIVMSG", channel, message) + } + */ + client.server.snomasks.Send(mask, message) +} diff --git a/irc/handlers.go b/irc/handlers.go index 7b0e1cf4..fa72d781 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -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() diff --git a/irc/server.go b/irc/server.go index 99629ab6..83198434 100644 --- a/irc/server.go +++ b/irc/server.go @@ -446,11 +446,6 @@ func (server *Server) tryRegister(c *Client) { return } - if connectionRequiresSasl(c) { - c.destroy(false) - return - } - // client MUST send PASS (or AUTHENTICATE, if skip-server-password is set) // before completing the other registration commands if !c.Authorized() { @@ -467,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 { diff --git a/oragono.yaml b/oragono.yaml index 6c5aa757..5be6746d 100644 --- a/oragono.yaml +++ b/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" From 9756ce02ba91031a458925e2c0dc1394cb93f741 Mon Sep 17 00:00:00 2001 From: moocow Date: Tue, 24 Apr 2018 20:51:20 +0200 Subject: [PATCH 3/3] fixes uint, moved code, stole slingnams hsNotifyChannel function --- irc/config.go | 8 +++--- irc/dnsbl.go | 63 +++++++++++++++--------------------------------- irc/server.go | 14 ++++++++++- irc/utils/net.go | 17 +++++++++++++ 4 files changed, 54 insertions(+), 48 deletions(-) diff --git a/irc/config.go b/irc/config.go index 443cbb80..db1d6fc7 100644 --- a/irc/config.go +++ b/irc/config.go @@ -101,18 +101,18 @@ type DnsblListEntry struct { Types []string Reply map[string]DnsblListReply Action string - ActionType uint + ActionType DnsblActionType Reason string } type DnsblListReply struct { Action string - ActionType uint + ActionType DnsblActionType Reason string } -func (conf *Config) DnsblTypes(action string) (uint, error) { - actions := map[string]uint{ +func (conf *Config) DnsblTypes(action string) (DnsblActionType, error) { + actions := map[string]DnsblActionType{ "require-sasl": DnsblRequireSaslReply, "allow": DnsblAllowReply, "block": DnsblBlockReply, diff --git a/irc/dnsbl.go b/irc/dnsbl.go index 9dab072a..e3cd9779 100644 --- a/irc/dnsbl.go +++ b/irc/dnsbl.go @@ -7,41 +7,27 @@ import ( "strings" "github.com/oragono/oragono/irc/sno" + "github.com/oragono/oragono/irc/utils" ) // Constants +type DnsblActionType uint + const ( - DnsblRequireSaslReply uint = iota + DnsblRequireSaslReply DnsblActionType = iota DnsblAllowReply DnsblBlockReply DnsblNotifyReply DnsblUnknownReply ) -// 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() -} - // 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", ReverseAddress(client.IP()), list.Host)) + res, err := net.LookupHost(fmt.Sprintf("%s.%s", utils.ReverseAddress(client.IP()), list.Host)) var entries []string if err != nil { - server.logger.Info("dnsbl-lookup", fmt.Sprintf("DNSBL loopup failed: %s", err)) + // An error may indicate that the A record was not found return entries } @@ -65,12 +51,11 @@ func (server *Server) ProcessBlacklist(client *Client) { return } - channel := server.DnsblConfig().Channel lists := server.DnsblConfig().Lists type DnsblTypeResponse struct { Host string - ActionType uint + ActionType DnsblActionType Reason string } var items = []DnsblTypeResponse{} @@ -98,54 +83,46 @@ func (server *Server) ProcessBlacklist(client *Client) { item := items[0] switch item.ActionType { case DnsblRequireSaslReply: - client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Connecting client %s matched %s, requiring SASL to proceed", client.IP(), item.Host)) + dnsblSendServiceMessage(server, fmt.Sprintf("Connecting client %s matched %s, requiring SASL to proceed", client.IP(), item.Host)) client.SetRequireSasl(true, item.Reason) case DnsblBlockReply: - client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Connecting client %s matched %s - killing", client.IP(), item.Host)) + 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: - client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Connecting client %s matched %s", client.IP(), item.Host)) + dnsblSendServiceMessage(server, fmt.Sprintf("Connecting client %s matched %s", client.IP(), item.Host)) case DnsblAllowReply: - client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Allowing host %s [%s]", client.IP(), item.Host)) + dnsblSendServiceMessage(server, fmt.Sprintf("Allowing host %s [%s]", client.IP(), item.Host)) } } return } -func connectionRequiresSasl(client *Client) bool { +func ConnectionRequiresSasl(client *Client) bool { sasl, reason := client.RequireSasl() if !sasl { return false } - channel := client.server.DnsblConfig().Channel - if client.Account() == "" { - //client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Connecting client %s and did not authenticate through SASL - blocking connection", client.IP())) + 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 } - client.sendServerMessage("", channel, sno.Dnsbl, fmt.Sprintf("Connecting client %s authenticated through SASL - allowing", client.IP())) + dnsblSendServiceMessage(client.server, fmt.Sprintf("Connecting client %s authenticated through SASL - allowing", client.IP())) return false } -func (client *Client) sendServerMessage(pseudo string, channel string, mask sno.Mask, message string) { - /* - This causes an out of bounds error - possibly in client.Send() - investigate further - if pseudo == "" { - pseudo = client.server.name - } - - if channel != "" { - client.Send(nil, pseudo, "PRIVMSG", channel, message) - } - */ - client.server.snomasks.Send(mask, message) +func dnsblSendServiceMessage(server *Server, message string) { + channel := server.DnsblConfig().Channel + if channel != "" { + server.serviceNotifyChannel(server.name, channel, message) + } + server.snomasks.Send(sno.Dnsbl, message) } diff --git a/irc/server.go b/irc/server.go index 83198434..f93a3839 100644 --- a/irc/server.go +++ b/irc/server.go @@ -463,7 +463,7 @@ func (server *Server) tryRegister(c *Client) { } // Check if connection requires SASL - if connectionRequiresSasl(c) { + if ConnectionRequiresSasl(c) { c.destroy(false) return } @@ -1230,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 diff --git a/irc/utils/net.go b/irc/utils/net.go index bbc19898..938d67f6 100644 --- a/irc/utils/net.go +++ b/irc/utils/net.go @@ -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() +}