Putting something in the validate_password() callback works well for logging password failures, but it would be called on every auth attempt, not after all the attempts were exhausted. Also, there would be no way at that point to write a message back to the client.
The typical way this is done is to print an auth banner before you begin authentication. You can do this by calling conn.send_auth_banner() from within your SSHServer’s begin_auth() callback. I just did an experiment here and it looks like OpenSSH refuses to print this banner if you try to send it after authentication has started. So, calling conn.send_auth_banner() from validate_password() isn’t likely to work.
I think you’d probably need to use keyboard-interactive authentication to do this, implementing the get_kbdint_challenge() and validate_kbdint_response() callbacks. Since OpenSSH only tries a max of 3 times for this auth method, you’d probably only be able to allow for two passwords failures, and then you could replace the third challenge with a message that you wanted to send instead which didn’t include any prompt. The client will print that message and then report that permission was denied:
(user123@localhost) Password:
(user123@localhost) Password:
Your message here
user123@localhost: Permission denied (keyboard-interactive).
The methods in your SSHServer subclass would look something like:
def connection_made(self, conn):
self._conn = conn
self._attempts = 2
def begin_auth(self, username):
return True
def kbdint_auth_supported(self):
return True
def get_kbdint_challenge(self, username, lang, submethods):
if self._attempts == 0:
return '', ‘Final failure message here', '', []
else:
self._attempts -= 1
return '', '', '', [('Password:', False)]
def validate_kbdint_response(self, username, responses):
# Check password in responses[0] for username and return True/False
return False