summaryrefslogtreecommitdiffstats
path: root/pam.py
diff options
context:
space:
mode:
Diffstat (limited to 'pam.py')
-rw-r--r--pam.py170
1 files changed, 170 insertions, 0 deletions
diff --git a/pam.py b/pam.py
new file mode 100644
index 0000000..d72489d
--- /dev/null
+++ b/pam.py
@@ -0,0 +1,170 @@
+# (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.
+
+---
+modified to work [better] with python3.2, 2011-12-6, david ford, <david@blue-labs.org>
+i haven't paid any attention to making sure things work in python2. there may be
+problems in my_conv()
+---
+Just some encoding fixes for python3, 2012-01-27 Matthias Schiffer <mschiffer@universe-factory.net>
+"""
+
+import sys
+import locale
+if sys.version_info >= (3,):
+ py3k = True
+else:
+ py3k = False
+
+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
+from ctypes import memmove
+from ctypes.util import find_library
+
+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)
+ ]
+
+# Various constants
+PAM_PROMPT_ECHO_OFF = 1
+PAM_PROMPT_ECHO_ON = 2
+PAM_ERROR_MSG = 3
+PAM_TEXT_INFO = 4
+
+LIBC = CDLL(find_library("c"))
+LIBPAM = CDLL(find_library("pam"))
+
+CALLOC = LIBC.calloc
+CALLOC.restype = c_void_p
+CALLOC.argtypes = [c_uint, c_uint]
+
+PAM_START = LIBPAM.pam_start
+PAM_START.restype = c_int
+PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)]
+
+PAM_STRERROR = LIBPAM.pam_strerror
+PAM_STRERROR.restype = c_char_p
+PAM_STRERROR.argtypes = [POINTER(PamHandle), c_int]
+
+PAM_AUTHENTICATE = LIBPAM.pam_authenticate
+PAM_AUTHENTICATE.restype = c_int
+PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
+
+class pam():
+ code = 0
+ reason = None
+
+ def __init__(self):
+ pass
+
+ def authenticate(self, username, password, service='login'):
+ """username and password authenticate for the given service.
+
+ Returns True for success, or False. self.code is the integer
+ value representing the numerice failure reason, or 0 on success.
+ self.reason is the textual reason.
+
+ Python3 expects bytes() for ctypes inputs. This function will make
+ necessary conversions using the latin-1 coding.
+
+ 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:
+ cs = c_char_p(password)
+ dst = CALLOC(sizeof(c_char_p), len(password)+1)
+ memmove(dst , cs, len(password))
+ p_response.contents[i].resp = dst
+ p_response.contents[i].resp_retcode = 0
+ return 0
+
+ # python3 ctypes prefers bytes, pretend everyone will be happy using latin-1
+ if py3k:
+ if isinstance(username, str):
+ username = bytes(username, locale.getpreferredencoding())
+ if isinstance(password, str):
+ password = bytes(password, locale.getpreferredencoding())
+ if isinstance(service, str):
+ service = bytes(service, locale.getpreferredencoding())
+
+ handle = PamHandle()
+ conv = PamConv(my_conv, 0)
+ retval = PAM_START(service, username, pointer(conv), pointer(handle))
+
+ if retval != 0:
+ # This is not an authentication error, something has gone wrong starting up PAM
+ self.code = retval
+ self.reason = str(PAM_STRERROR(pointer(handle), retval), locale.getpreferredencoding())
+ return False
+
+ retval = PAM_AUTHENTICATE(handle, 0)
+
+ if retval == 0:
+ # success
+ logic = True
+ else:
+ logic = False
+
+ # store information to inform the caller why we failed
+ self.code = retval
+ self.reason = str(PAM_STRERROR(pointer(handle), retval), locale.getpreferredencoding())
+
+ return logic