Repository: fail2ban/fail2ban

Issue:
Failing to start service returns 0
### Environment:
- Fail2Ban version (including any possible distribution suffixes): Fail2Ban v0.10.2
- OS, including release name/version: Ubuntu 18.04
- [x] Fail2Ban installed via OS/distribution mechanisms
- [x] You have not applied any additional foreign patches to the codebase

### The issue:
When starting the service fails (using `service fail2ban start` or `/etc/init.d/fail2ban start`, the error code is still 0. This is quite a problem for automation, for example using Ansible, as failing programs are meant to return non-zero codes. The program properly compains over `stderr`, but `stderr` is not a proper way to check for errors in the sense that it can be reliably used by other programs (it is normal for programs to succeed but return debugging info or warnings over `stderr` for example). It would be nice if fail2ban could properly return a non-zero exit code.

#### Steps to reproduce
Start a Docker container using the `ubuntu:18.04` image, and install fail2ban using the package manager. Now run `service fail2ban start` or `/etc/init.d/fail2ban start`.

#### Expected behavior
It expects to find a file at `/var/log/auth.log`, so we should get some error and a non-zero exit code.

#### Observed behavior
We do get an error:
```
 * Starting authentication failure monitor fail2ban
 No file(s) found for glob /var/log/auth.log
 Failed during configuration: Have not found any log file for sshd jail
   ...fail!
```

But the exit code (`echo $?`) is `0`.


Discussion:
I cannot reproduce it, I get always `255` as error code in such case, no matter how I try to start it (via client, via server, as fork as well as in foreground):
```
$ sudo fail2ban-client start; echo $?
ERROR   Failed during configuration: Have not found any log file for exim jail
255

$ sudo fail2ban-server; echo $?
ERROR   Failed during configuration: Have not found any log file for exim jail
ERROR   Async configuration of server failed
255

$ sudo fail2ban-server -f; echo $?
ERROR   Failed during configuration: Have not found any log file for exim jail
ERROR   Async configuration of server failed
255
```

So how your init-script does start the fail2ban-service?

We have also client-parameter `-t, --test` to check you've no one error in configuration of fail2ban.
What do you see here:
```
fail2ban-client --test; echo $?
```
> So how your init-script does start the fail2ban-service?

I don't know, I'm just using the standard package for Ubuntu 18.04.
The init script is definitely the problem though, I ran your command:

```
# fail2ban-client --test; echo $?
 No file(s) found for glob /var/log/auth.log
 Failed during configuration: Have not found any log file for sshd jail
 ERROR: test configuration failed
255
# service fail2ban start; echo $?
 * Starting authentication failure monitor fail2ban
 No file(s) found for glob /var/log/auth.log
 Failed during configuration: Have not found any log file for sshd jail
   ...fail!
0
```
Looking at the init script:

The exit code of this line https://github.com/fail2ban/fail2ban/blob/b5dd5adb08f9fd3496f8c63b1d53b0e3c6aabbf1/files/debian-initd#L193 is `2`, but that of the next line is `0`.

Changing this line https://github.com/fail2ban/fail2ban/blob/b5dd5adb08f9fd3496f8c63b1d53b0e3c6aabbf1/files/debian-initd#L194 to `return $?` fixes it to me. I'm not sure what it's trying to achieve right now, it calls `log_end_msg_wrapper` which calls `log_end_msg` which seems undefined; I can't find it in any case.
@yarikoptic Can you take a look here, please...

Resolving patch:
diff --git a/fail2ban/client/fail2banclient.py b/fail2ban/client/fail2banclient.py
--- a/fail2ban/client/fail2banclient.py
+++ b/fail2ban/client/fail2banclient.py
@@ -69,7 +69,7 @@ def __sigTERMhandler(self, signum, frame): # pragma: no cover
 		# Print a new line because we probably come from wait
 		output("")
 		logSys.warning("Caught signal %d. Exiting" % signum)
-		exit(-1)
+		exit(255)
 
 	def __ping(self, timeout=0.1):
 		return self.__processCmd([["ping"] + ([timeout] if timeout != -1 else [])],
@@ -500,5 +500,5 @@ def exec_command_line(argv):
 	if client.start(argv):
 		exit(0)
 	else:
-		exit(-1)
+		exit(255)
 
diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py
--- a/fail2ban/client/fail2banregex.py
+++ b/fail2ban/client/fail2banregex.py
@@ -668,7 +668,7 @@ def exec_command_line(*args):
 	if errors:
 		sys.stderr.write("\n".join(errors) + "\n\n")
 		parser.print_help()
-		sys.exit(-1)
+		sys.exit(255)
 
 	output( "" )
 	output( "Running tests" )
@@ -696,4 +696,4 @@ def exec_command_line(*args):
 
 	fail2banRegex = Fail2banRegex(opts)
 	if not fail2banRegex.start(args):
-		sys.exit(-1)
+		sys.exit(255)
diff --git a/fail2ban/client/fail2banserver.py b/fail2ban/client/fail2banserver.py
--- a/fail2ban/client/fail2banserver.py
+++ b/fail2ban/client/fail2banserver.py
@@ -212,7 +212,7 @@ def _server_ready():
 				if not phase.get('done', False):
 					if server: # pragma: no cover
 						server.quit()
-					exit(-1)
+					exit(255)
 				if background:
 					logSys.debug('Starting server done')
 
@@ -223,7 +223,7 @@ def _server_ready():
 				logSys.error(e)
 			if server: # pragma: no cover
 				server.quit()
-			exit(-1)
+			exit(255)
 
 		return True
 
@@ -238,4 +238,4 @@ def exec_command_line(argv):
 	if server.start(argv):
 		exit(0)
 	else:
-		exit(-1)
+		exit(255)


Test patch:
diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py
--- a/fail2ban/tests/fail2banclienttestcase.py
+++ b/fail2ban/tests/fail2banclienttestcase.py
@@ -581,7 +581,7 @@ def testClientStartBackgroundCall(self, tmp):
 			# test reload missing jail (direct):
 			self.execCmd(FAILED, startparams, "reload", "~~unknown~jail~fail~~")
 			self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'")
-			self.assertLogged("Exit with code -1")
+			self.assertLogged("Exit with code 255")
 			self.pruneLog()
 		finally:
 			self.pruneLog()