summaryrefslogtreecommitdiffstats
path: root/funcweb/funcweb/identity/pam.py
diff options
context:
space:
mode:
Diffstat (limited to 'funcweb/funcweb/identity/pam.py')
-rw-r--r--funcweb/funcweb/identity/pam.py127
1 files changed, 127 insertions, 0 deletions
diff --git a/funcweb/funcweb/identity/pam.py b/funcweb/funcweb/identity/pam.py
new file mode 100644
index 0000000..aed5420
--- /dev/null
+++ b/funcweb/funcweb/identity/pam.py
@@ -0,0 +1,127 @@
+# (c) 2007 Chris AtLee <chris@atlee.ca>
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license.php
+"""
+PAM module for python
+
+Provides an authenticate function that will allow the caller to authenticate
+a user against the Pluggable Authentication Modules (PAM) on the system.
+
+Implemented using ctypes, so no compilation is necessary.
+"""
+__all__ = ['authenticate']
+
+from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
+from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
+
+
+LIBPAM = CDLL("libpam.so")
+LIBC = CDLL("libc.so.6")
+
+CALLOC = LIBC.calloc
+CALLOC.restype = c_void_p
+CALLOC.argtypes = [c_uint, c_uint]
+
+STRDUP = LIBC.strdup
+STRDUP.argstypes = [c_char_p]
+STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
+
+# Various constants
+PAM_PROMPT_ECHO_OFF = 1
+PAM_PROMPT_ECHO_ON = 2
+PAM_ERROR_MSG = 3
+PAM_TEXT_INFO = 4
+
+class PamHandle(Structure):
+ """wrapper class for pam_handle_t"""
+ _fields_ = [
+ ("handle", c_void_p)
+ ]
+
+ def __init__(self):
+ Structure.__init__(self)
+ self.handle = 0
+
+class PamMessage(Structure):
+ """wrapper class for pam_message structure"""
+ _fields_ = [
+ ("msg_style", c_int),
+ ("msg", c_char_p),
+ ]
+
+ def __repr__(self):
+ return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
+
+class PamResponse(Structure):
+ """wrapper class for pam_response structure"""
+ _fields_ = [
+ ("resp", c_char_p),
+ ("resp_retcode", c_int),
+ ]
+
+ def __repr__(self):
+ return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
+
+CONV_FUNC = CFUNCTYPE(c_int,
+ c_int, POINTER(POINTER(PamMessage)),
+ POINTER(POINTER(PamResponse)), c_void_p)
+
+class PamConv(Structure):
+ """wrapper class for pam_conv structure"""
+ _fields_ = [
+ ("conv", CONV_FUNC),
+ ("appdata_ptr", c_void_p)
+ ]
+
+PAM_START = LIBPAM.pam_start
+PAM_START.restype = c_int
+PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
+ POINTER(PamHandle)]
+
+PAM_AUTHENTICATE = LIBPAM.pam_authenticate
+PAM_AUTHENTICATE.restype = c_int
+PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
+
+def authenticate(username, password, service='login'):
+ """Returns True if the given username and password authenticate for the
+ given service. Returns False otherwise
+
+ ``username``: the username to authenticate
+
+ ``password``: the password in plain text
+
+ ``service``: the PAM service to authenticate against.
+ Defaults to 'login'"""
+ @CONV_FUNC
+ def my_conv(n_messages, messages, p_response, app_data):
+ """Simple conversation function that responds to any
+ prompt where the echo is off with the supplied password"""
+ # Create an array of n_messages response objects
+ addr = CALLOC(n_messages, sizeof(PamResponse))
+ p_response[0] = cast(addr, POINTER(PamResponse))
+ for i in range(n_messages):
+ if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
+ pw_copy = STRDUP(password)
+ p_response.contents[i].resp = cast(pw_copy, c_char_p)
+ p_response.contents[i].resp_retcode = 0
+ return 0
+
+ # STRDUP expects byte strings
+ if isinstance(password, unicode):
+ password = str(password)
+
+ handle = PamHandle()
+ conv = PamConv(my_conv, 0)
+ retval = PAM_START(service, username, pointer(conv), pointer(handle))
+
+ if retval != 0:
+ # TODO: This is not an authentication error, something
+ # has gone wrong starting up PAM
+ return False
+
+ retval = PAM_AUTHENTICATE(handle, 0)
+ return retval == 0
+
+if __name__ == "__main__":
+ import getpass
+ print authenticate(getpass.getuser(), getpass.getpass())