#!/usr/bin/python3 # # This script implement's Dovecot's checkpassword authentication mechanism: # http://wiki2.dovecot.org/AuthDatabase/CheckPassword?action=show&redirect=PasswordDatabase%2FCheckPassword # # This allows us to perform our own password validation, such as for two-factor authentication, # which Dovecot does not have any native support for. # # We will issue an HTTP request to our management server to perform authentication. import sys, os, urllib.request, base64, json, traceback try: # Read fd 3 which provides the username and password separated # by NULLs and two other undocumented/empty fields. creds = b'' while True: b = os.read(3, 1024) if len(b) == 0: break creds += b email, pw, dummy, dummy = creds.split(b'\x00') # Call the management server's "/me" method with the # provided credentials req = urllib.request.Request('http://127.0.0.1:10222/me') req.add_header(b'Authorization', b'Basic ' + base64.b64encode(email + b':' + pw)) response = urllib.request.urlopen(req) # The response is always success and always a JSON object # indicating the authentication result. resp = response.read().decode('utf8') resp = json.loads(resp) if not isinstance(resp, dict): raise ValueError("Response is not a JSON object.") except: # Handle all exceptions. Print what happens (ends up in syslog, thanks # to dovecot) and return an exit status that indicates temporary failure, # which is passed on to the authenticating client. traceback.print_exc() print(json.dumps(dict(os.environ), indent=2), file=sys.stderr) sys.exit(111) if resp.get('status') != 'authorized': # Indicates login failure. # (sys.exit should not be inside the try block.) sys.exit(1) # Signal ok by executing the indicated process, per the Dovecot # protocol. (Note that the second parameter is the 0th argument # to the called process, which is required and is typically the # file itself.) os.execl(sys.argv[1], sys.argv[1])