diff --git a/confluent_server/confluent/auth.py b/confluent_server/confluent/auth.py index 7bc167ff..9e675fbb 100644 --- a/confluent_server/confluent/auth.py +++ b/confluent_server/confluent/auth.py @@ -26,6 +26,7 @@ import Cryptodome.Protocol.KDF as KDF from fnmatch import fnmatch import hashlib import hmac +import msgpack import multiprocessing import os import pwd @@ -100,6 +101,10 @@ _deniedbyrole = { } +class PromptsNeeded(Exception): + def __init__(self, prompts): + self.prompts = prompts + def _get_usertenant(name, tenant=False): """_get_usertenant @@ -278,22 +283,34 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant # to let a non-0 user check anothers password. # We will fork and the child will assume elevated privilege to # get unix_chkpwd helper to enable checking /etc/shadow + getprompt, sendprompt = os.pipe() + getprompt, sendprompt = os.fdopen(getprompt, 'rb', 0), os.fdopen(sendprompt, 'wb', 0) pid = os.fork() if not pid: usergood = False try: + getprompt.close() # we change to the uid we are trying to authenticate as, because # pam_unix uses unix_chkpwd which reque os.setuid(pwe.pw_uid) - usergood = pam.authenticate(user, passphrase, service=_pamservice) - except pam.PromptsNeeded: - os._exit(2) + pa = pam.pam() + usergood = pa.authenticate(user, passphrase, service=_pamservice) + if (not usergood and len(pa.prompts) > 1 and + (not isinstance(passphrase, dict) or + (set(passphrase) - pa.prompts))): + sendprompt.write(msgpack.packb(list(pa.prompts))) + sendprompt.close() + os._exit(2) finally: os._exit(0 if usergood else 1) + sendprompt.close() usergood = os.waitpid(pid, 0)[1] if (usergood >> 8) == 2: - pam.authenticate(user, passphrase, service=_pamservice) + prompts = getprompt.read() + if (prompts): + raise PromptsNeeded(msgpack.unpackb(prompts)) usergood = usergood == 0 + getprompt.close() else: # We are running as root, we don't need to fork in order to authenticate the # user diff --git a/confluent_server/confluent/pam.py b/confluent_server/confluent/pam.py index 26df13e8..24e75db5 100644 --- a/confluent_server/confluent/pam.py +++ b/confluent_server/confluent/pam.py @@ -104,17 +104,12 @@ pam_authenticate.restype = c_int pam_authenticate.argtypes = [PamHandle, c_int] -class PromptsNeeded(Exception): - def __init__(self, prompts): - self.prompts = prompts - - class pam(): code = 0 reason = None def __init__(self): - pass + self.prompts = set([]) def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True): """username and password authentication for the given service. @@ -145,17 +140,17 @@ class pam(): for i in range(n_messages): if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: currprompt = messages[i].contents.msg.decode('utf8').strip() - prompts.add(currprompt) + self.prompts.add(currprompt) for i in range(n_messages): if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: currprompt = messages[i].contents.msg.decode('utf8').strip() if isinstance(password, dict): if currprompt in password: continue - elif len(prompts) > 1: + elif len(self.prompts) > 1: return 19 # PAM_CONV_ERR else: - if len(prompts) > 1: + if len(self.prompts) > 1: return 19 # PAM_CONV_ERR # Create an array of n_messages response objects addr = calloc(n_messages, sizeof(PamResponse)) @@ -197,10 +192,6 @@ class pam(): self.reason = 'strings may not contain NUL' return False - # do this up front so we can safely throw an exception if there's - # anything wrong with it - prompts = set([]) - handle = PamHandle() conv = PamConv(my_conv, 0) retval = pam_start(service, username, byref(conv), byref(handle)) @@ -229,8 +220,6 @@ class pam(): if hasattr(libpam, 'pam_end'): pam_end(handle, retval) - if (not isinstance(password, dict)) and len(prompts) > 1 and not auth_success: - raise PromptsNeeded(prompts) return auth_success