Repository: fail2ban/fail2ban

Issue:
0.10 - deprecated DNSUtils.IsValidIP in fakegooglebot ignore command
### Environment:
- Fail2Ban version (including any possible distribution suffixes):  0.10.0a2 0a7374dec69bbbe54e0180c80c8d3b653fd1012f
- OS, including release ame/version: Debian 3.2.81-2 x86_64 GNU/Linux
- [ ] Fail2Ban installed via OS/distribution mechanisms
- [X] You have not applied any additional foreign patches to the codebase
- [ ] Some customizations were done to the configuration (provide details below is so)
### The issue:

Deprecated DNSUtils.isValidIP method used at 
[ignorecommands/apache-fakegooglebot#L18](https://github.com/fail2ban/fail2ban/blob/d71a525a851286392297ffcadb06159e9ac405ba/config/filter.d/ignorecommands/apache-fakegooglebot#L18) 
#### Steps to reproduce

``` bash
/etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 66.249.65.52; echo $?
```
#### Expected behavior

`0`
#### Observed behavior

```
Traceback (most recent call last):
  File "/etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot", line 35, in <module>
    is_googlebot(process_args(sys.argv))
  File "/etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot", line 18, in process_args
    if not DNSUtils.isValidIP(ip):
AttributeError: class DNSUtils has no attribute 'isValidIP'
```
#### Any additional information
### Configuration, dump and another helpful excerpts
#### Any customizations done to /etc/fail2ban/ configuration
#### Relevant parts of /var/log/fail2ban.log file:

```
2016-09-23 23:34:07,817 fail2ban.utils          [11711]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 66.249.65.52 -- stderr:
2016-09-23 23:34:07,818 fail2ban.utils          [11711]: ERROR    -- stderr: 'Traceback (most recent call last):'
2016-09-23 23:34:07,818 fail2ban.utils          [11711]: ERROR    -- stderr: '  File "/etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot", line 35, in <module>'
2016-09-23 23:34:07,818 fail2ban.utils          [11711]: ERROR    -- stderr: '    is_googlebot(process_args(sys.argv))'
2016-09-23 23:34:07,818 fail2ban.utils          [11711]: ERROR    -- stderr: '  File "/etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot", line 18, in process_a
rgs'
2016-09-23 23:34:07,818 fail2ban.utils          [11711]: ERROR    -- stderr: '    if not DNSUtils.isValidIP(ip):'
2016-09-23 23:34:07,818 fail2ban.utils          [11711]: ERROR    -- stderr: "AttributeError: class DNSUtils has no attribute 'isValidIP'"
2016-09-23 23:34:07,819 fail2ban.utils          [11711]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 66.249.65.52 -- returned 1
```



Discussion:
Could be fixed like this:

``` diff
@@ -14,8 +14,8 @@

     ip = argv[1]

-    from fail2ban.server.ipdns import DNSUtils
-    if not DNSUtils.isValidIP(ip):
+    from fail2ban.server.ipdns import IPAddr
+    if not IPAddr(ip).isValid:
        sys.stderr.write("Argument must be a single valid IP. Got: %s\n"
                         % ip)
        sys.exit(3)
```


Resolving patch:
diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py
--- a/fail2ban/server/actions.py
+++ b/fail2ban/server/actions.py
@@ -28,10 +28,6 @@
 import os
 import sys
 import time
-if sys.version_info >= (3, 3):
-	import importlib.machinery
-else:
-	import imp
 from collections import Mapping
 try:
 	from collections import OrderedDict
@@ -87,18 +83,11 @@ def __init__(self, jail):
 
 	@staticmethod
 	def _load_python_module(pythonModule):
-		pythonModuleName = os.path.splitext(
-			os.path.basename(pythonModule))[0]
-		if sys.version_info >= (3, 3):
-			mod = importlib.machinery.SourceFileLoader(
-				pythonModuleName, pythonModule).load_module()
-		else:
-			mod = imp.load_source(
-				pythonModuleName, pythonModule)
-		if not hasattr(mod, "Action"):
+		mod = Utils.load_python_module(pythonModule)
+		if not hasattr(mod, "Action"): # pragma: no cover
 			raise RuntimeError(
 				"%s module does not have 'Action' class" % pythonModule)
-		elif not issubclass(mod.Action, ActionBase):
+		elif not issubclass(mod.Action, ActionBase): # pragma: no cover
 			raise RuntimeError(
 				"%s module %s does not implement required methods" % (
 					pythonModule, mod.Action.__name__))
diff --git a/fail2ban/server/utils.py b/fail2ban/server/utils.py
--- a/fail2ban/server/utils.py
+++ b/fail2ban/server/utils.py
@@ -30,6 +30,11 @@
 import time
 from ..helpers import getLogger, uni_decode
 
+if sys.version_info >= (3, 3):
+	import importlib.machinery
+else:
+	import imp
+
 # Gets the instance of the logger.
 logSys = getLogger(__name__)
 
@@ -290,3 +295,15 @@ def pid_exists(pid):
 				return True
 			else:
 				return False
+
+	@staticmethod
+	def load_python_module(pythonModule):
+		pythonModuleName = os.path.splitext(
+			os.path.basename(pythonModule))[0]
+		if sys.version_info >= (3, 3):
+			mod = importlib.machinery.SourceFileLoader(
+				pythonModuleName, pythonModule).load_module()
+		else:
+			mod = imp.load_source(
+				pythonModuleName, pythonModule)
+		return mod
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -205,7 +205,7 @@ def run(self):
 			glob("config/filter.d/*.conf")
 		),
 		('/etc/fail2ban/filter.d/ignorecommands',
-			glob("config/filter.d/ignorecommands/*")
+			[p for p in glob("config/filter.d/ignorecommands/*") if isfile(p)]
 		),
 		('/etc/fail2ban/action.d',
 			glob("config/action.d/*.conf") +


Test patch:
diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py
--- a/fail2ban/tests/filtertestcase.py
+++ b/fail2ban/tests/filtertestcase.py
@@ -48,6 +48,9 @@
 
 TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
 
+STOCK_CONF_DIR = "config"
+STOCK = os.path.exists(os.path.join(STOCK_CONF_DIR, 'fail2ban.conf'))
+
 
 # yoh: per Steven Hiscocks's insight while troubleshooting
 # https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
@@ -409,6 +412,33 @@ def testIgnoreIPDNSNOK(self):
 		self.assertFalse(self.filter.inIgnoreIPList("128.178.50.11"))
 		self.assertFalse(self.filter.inIgnoreIPList("128.178.50.13"))
 
+	def testIgnoreCmdApacheFakegooglebot(self):
+		if not STOCK: # pragma: no cover
+			raise unittest.SkipTest('Skip test because of no STOCK config')
+		cmd = os.path.join(STOCK_CONF_DIR, "filter.d/ignorecommands/apache-fakegooglebot")
+		## below test direct as python module:
+		mod = Utils.load_python_module(cmd)
+		self.assertFalse(mod.is_googlebot(mod.process_args([cmd, "128.178.50.12"])))
+		self.assertFalse(mod.is_googlebot(mod.process_args([cmd, "192.0.2.1"])))
+		bot_ips = ['66.249.66.1']
+		for ip in bot_ips:
+			self.assertTrue(mod.is_googlebot(mod.process_args([cmd, str(ip)])), "test of googlebot ip %s failed" % ip)
+		self.assertRaises(ValueError, lambda: mod.is_googlebot(mod.process_args([cmd])))
+		self.assertRaises(ValueError, lambda: mod.is_googlebot(mod.process_args([cmd, "192.0"])))
+		## via command:
+		self.filter.setIgnoreCommand(cmd + " <ip>")
+		for ip in bot_ips:
+			self.assertTrue(self.filter.inIgnoreIPList(str(ip)), "test of googlebot ip %s failed" % ip)
+			self.assertLogged('-- returned successfully')
+			self.pruneLog()
+		self.assertFalse(self.filter.inIgnoreIPList("192.0"))
+		self.assertLogged('Argument must be a single valid IP.')
+		self.pruneLog()
+		self.filter.setIgnoreCommand(cmd + " bad arguments <ip>")
+		self.assertFalse(self.filter.inIgnoreIPList("192.0"))
+		self.assertLogged('Please provide a single IP as an argument.')
+
+
 
 class LogFile(LogCaptureTestCase):