summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2014-07-14 14:39:00 -0400
committerPetr Vobornik <pvoborni@redhat.com>2014-07-21 16:36:28 +0200
commite4771302812388cc7f9773ce48d0bc3b34855248 (patch)
tree4f82eb0042e21addc4d6fd570d3406ee93eb17ef
parentad593a5c06d447006f14446cbdfbf5b437a0d111 (diff)
downloadfreeipa-e4771302812388cc7f9773ce48d0bc3b34855248.tar.gz
freeipa-e4771302812388cc7f9773ce48d0bc3b34855248.tar.xz
freeipa-e4771302812388cc7f9773ce48d0bc3b34855248.zip
Fix login password expiration detection with OTP
The preexisting code would execute two steps. First, it would perform a kinit. If the kinit failed, it would attempt to bind using the same credentials to determine if the password were expired. While this method is fairly ugly, it mostly worked in the past. However, with OTP this breaks. This is because the OTP code is consumed by the kinit step. But because the password is expired, the kinit step fails. When the bind is executed, the OTP token is already consumed, so bind fails. This causes all password expirations to be reported as invalid credentials. After discussion with MIT, the best way to handle this case with the standard tools is to set LC_ALL=C and check the output from the command. This eliminates the bind step altogether. The end result is that OTP works and all password failures are more performant. https://fedorahosted.org/freeipa/ticket/4412 Reviewed-By: Petr Vobornik <pvoborni@redhat.com>
-rw-r--r--ipalib/errors.py6
-rw-r--r--ipaserver/rpcserver.py40
2 files changed, 15 insertions, 31 deletions
diff --git a/ipalib/errors.py b/ipalib/errors.py
index d69e33062..09b7779e9 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -584,6 +584,12 @@ class InvalidSessionPassword(SessionError):
errno = 1201
format= _('Principal %(principal)s cannot be authenticated: %(message)s')
+class PasswordExpired(InvalidSessionPassword):
+ """
+ **1202** Raised when we cannot obtain a TGT for a principal because the password is expired.
+ """
+ errno = 1202
+
##############################################################################
# 2000 - 2999: Authorization errors
class AuthorizationError(PublicError):
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 454a53451..18de23d3a 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -39,7 +39,7 @@ from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
from ipalib.backend import Executioner
from ipalib.errors import (PublicError, InternalError, CommandError, JSONError,
CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError,
- ExecutionError)
+ ExecutionError, PasswordExpired)
from ipalib.request import context, destroy_context
from ipalib.rpc import (xml_dumps, xml_loads,
json_encode_binary, json_decode_binary)
@@ -944,37 +944,12 @@ class login_password(Backend, KerberosSession, HTTP_Status):
# Get the ccache we'll use and attempt to get credentials in it with user,password
ipa_ccache_name = get_ipa_ccache_name()
- reason = 'invalid-password'
try:
self.kinit(user, self.api.env.realm, password, ipa_ccache_name)
- except InvalidSessionPassword, e:
- # Ok, now why is this bad. Is the password simply bad or is the
- # password expired?
- try:
- dn = DN(('uid', user),
- self.api.env.container_user,
- self.api.env.basedn)
- conn = ldap2(shared_instance=False,
- ldap_uri=self.api.env.ldap_uri)
- conn.connect(bind_dn=dn, bind_pw=password)
-
- # password is ok, must be expired, lets double-check
- entry_attrs = conn.get_entry(dn,
- ['krbpasswordexpiration'])
- if 'krbpasswordexpiration' in entry_attrs:
- expiration = entry_attrs['krbpasswordexpiration'][0]
- if expiration <= datetime.datetime.utcnow():
- reason = 'password-expired'
-
- except Exception:
- # It doesn't really matter how we got here but the user's
- # password is not accepted or the user is unknown.
- pass
- finally:
- if conn.isconnected():
- conn.destroy_connection()
-
- return self.unauthorized(environ, start_response, str(e), reason)
+ except PasswordExpired as e:
+ return self.unauthorized(environ, start_response, str(e), 'password-expired')
+ except InvalidSessionPassword as e:
+ return self.unauthorized(environ, start_response, str(e), 'invalid-password')
return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response)
@@ -1001,7 +976,8 @@ class login_password(Backend, KerberosSession, HTTP_Status):
(stdout, stderr, returncode) = ipautil.run(
[paths.KINIT, principal, '-T', armor_path],
- env={'KRB5CCNAME': ccache_name}, stdin=password, raiseonerr=False)
+ env={'KRB5CCNAME': ccache_name, 'LC_ALL': 'C'},
+ stdin=password, raiseonerr=False)
self.debug('kinit: principal=%s returncode=%s, stderr="%s"',
principal, returncode, stderr)
@@ -1013,6 +989,8 @@ class login_password(Backend, KerberosSession, HTTP_Status):
raiseonerr=False)
if returncode != 0:
+ if stderr.strip() == 'kinit: Cannot read password while getting initial credentials':
+ raise PasswordExpired(principal=principal, message=unicode(stderr))
raise InvalidSessionPassword(principal=principal, message=unicode(stderr))
class change_password(Backend, HTTP_Status):