Repository: fail2ban/fail2ban

Issue:
apache-fakegooglebot - stdout: ''
I get these in the fail2ban.log

```
2015-09-23 01:37:30,065 fail2ban.action         [1661]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 5.39.222.253 -- stdout: ''
2015-09-23 01:37:30,066 fail2ban.action         [1661]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 5.39.222.253 -- stderr: ''
2015-09-23 01:37:30,066 fail2ban.action         [1661]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 5.39.222.253 -- returned 1
2015-09-23 01:37:30,462 fail2ban.actions        [1661]: NOTICE  [apache-fakegooglebot] Ban 5.39.222.253
```
matching entry in access_log is
```
5.39.222.253 - - [23/Sep/2015:01:37:29 +0200] "GET / HTTP/1.0" 200 49 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
```
fail2ban comes from FC22:
fail2ban-0.9.3-1.fc22.noarch
fail2ban-server-0.9.3-1.fc22.noarch
fail2ban-sendmail-0.9.3-1.fc22.noarch
fail2ban-firewalld-0.9.3-1.fc22.noarch



Discussion:
This script can return 1 (without stdout/stderr) if host resolved for the IP adress does not match `crawl-.*\.googlebot\.com` (see https://github.com/fail2ban/fail2ban/blob/master/config/filter.d/ignorecommands/apache-fakegooglebot#L29).
I think, it can be expected situation, but at the moment I don't know how the logging of this "error" can be prevented.

@yarikoptic do you know by chance, it was always that way, or approximately it is result of #1155 (resp. #1187)?

Still happening.

```
2016-09-02 21:26:30,774 fail2ban.actions        [1341]: NOTICE  [apache-fakegooglebot] 139.59.31.211 already banned
2016-09-02 21:26:30,879 fail2ban.action         [1341]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 139.59.31.211 -- stdout: ''
2016-09-02 21:26:30,880 fail2ban.action         [1341]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 139.59.31.211 -- stderr: ''
2016-09-02 21:26:30,880 fail2ban.action         [1341]: ERROR   /etc/fail2ban/filter.d/ignorecommands/apache-fakegooglebot 139.59.31.211 -- returned 1
2016-09-02 21:26:30,881 fail2ban.filter         [1341]: INFO    [apache-fakegooglebot] Found 139.59.31.211
2016-09-02 21:26:31,776 fail2ban.actions        [1341]: NOTICE  [apache-fakegooglebot] 139.59.31.211 already banned
```

I'm seeing this on my fresh CentOS7 install with fail2ban-0.9.5-3.el7.noarch.  The good news is that it does appear to actually ban the bogus attempts.  but the extraneous errors are annoying.


Resolving patch:
diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py
--- a/fail2ban/server/action.py
+++ b/fail2ban/server/action.py
@@ -584,7 +584,7 @@ def _processCmd(self, cmd, aInfo=None, conditional=''):
 		return self.executeCmd(realCmd, self.timeout)
 
 	@staticmethod
-	def executeCmd(realCmd, timeout=60):
+	def executeCmd(realCmd, timeout=60, **kwargs):
 		"""Executes a command.
 
 		Parameters
@@ -613,6 +613,6 @@ def executeCmd(realCmd, timeout=60):
 
 		_cmd_lock.acquire()
 		try:
-			return Utils.executeCmd(realCmd, timeout, shell=True, output=False)
+			return Utils.executeCmd(realCmd, timeout, shell=True, output=False, **kwargs)
 		finally:
 			_cmd_lock.release()
diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -455,7 +455,8 @@ def inIgnoreIPList(self, ip, log_ignore=False):
 		if self.__ignoreCommand:
 			command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
 			logSys.debug('ignore command: ' + command)
-			ret_ignore = CommandAction.executeCmd(command)
+			ret, ret_ignore = CommandAction.executeCmd(command, success_codes=(0, 1))
+			ret_ignore = ret and ret_ignore == 0
 			self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
 			return ret_ignore
 
diff --git a/fail2ban/server/utils.py b/fail2ban/server/utils.py
--- a/fail2ban/server/utils.py
+++ b/fail2ban/server/utils.py
@@ -110,7 +110,7 @@ def setFBlockMode(fhandle, value):
 		return flags
 
 	@staticmethod
-	def executeCmd(realCmd, timeout=60, shell=True, output=False, tout_kill_tree=True):
+	def executeCmd(realCmd, timeout=60, shell=True, output=False, tout_kill_tree=True, success_codes=(0,)):
 		"""Executes a command.
 
 		Parameters
@@ -178,7 +178,7 @@ def _popen_wait_end():
 			if not popen:
 				return False if not output else (False, stdout, stderr, retcode)
 
-		std_level = retcode == 0 and logging.DEBUG or logging.ERROR
+		std_level = logging.DEBUG if retcode in success_codes else logging.ERROR
 		# if we need output (to return or to log it): 
 		if output or std_level >= logSys.getEffectiveLevel():
 			# if was timeouted (killed/terminated) - to prevent waiting, set std handles to non-blocking mode.
@@ -208,8 +208,8 @@ def _popen_wait_end():
 				popen.stderr.close()
 
 		success = False
-		if retcode == 0:
-			logSys.debug("%-.40s -- returned successfully", realCmd)
+		if retcode in success_codes:
+			logSys.debug("%-.40s -- returned successfully %i", realCmd, retcode)
 			success = True
 		elif retcode is None:
 			logSys.error("%-.40s -- unable to kill PID %i", realCmd, popen.pid)
@@ -223,7 +223,9 @@ def _popen_wait_end():
 			logSys.error("%-.40s -- returned %i", realCmd, retcode)
 			if msg:
 				logSys.info("HINT on %i: %s", retcode, msg % locals())
-		return success if not output else (success, stdout, stderr, retcode)
+		if output:
+			return success, stdout, stderr, retcode
+		return success if len(success_codes) == 1 else (success, retcode)
 	
 	@staticmethod
 	def wait_for(cond, timeout, interval=None):


Test patch:
diff --git a/fail2ban/tests/files/ignorecommand.py b/fail2ban/tests/files/ignorecommand.py
--- a/fail2ban/tests/files/ignorecommand.py
+++ b/fail2ban/tests/files/ignorecommand.py
@@ -1,5 +1,8 @@
 #!/usr/bin/env fail2ban-python
 import sys
+if len(sys.argv) != 2 or sys.argv[1] == "":
+	sys.stderr.write('usage: ignorecommand IP')
+	exit(10)
 if sys.argv[1] == "10.0.0.1":
 	exit(0)
 exit(1)
diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py
--- a/fail2ban/tests/filtertestcase.py
+++ b/fail2ban/tests/filtertestcase.py
@@ -377,6 +377,10 @@ def testIgnoreCommand(self):
 		self.filter.setIgnoreCommand(sys.executable + ' ' + os.path.join(TEST_FILES_DIR, "ignorecommand.py <ip>"))
 		self.assertTrue(self.filter.inIgnoreIPList("10.0.0.1"))
 		self.assertFalse(self.filter.inIgnoreIPList("10.0.0.0"))
+		self.assertLogged("returned successfully 0", "returned successfully 1", all=True)
+		self.pruneLog()
+		self.assertFalse(self.filter.inIgnoreIPList(""))
+		self.assertLogged("usage: ignorecommand IP", "returned 10", all=True)
 
 	def testIgnoreCauseOK(self):
 		ip = "93.184.216.34"