0001# (c) 2007 Chris AtLee <chris@atlee.ca>
0002# Licensed under the MIT license:
0003# http://www.opensource.org/licenses/mit-license.php
0004"""
0005PAM module for python
0006
0007Provides an authenticate function that will allow the caller to authenticate
0008a user against the Pluggable Authentication Modules (PAM) on the system.
0009
0010Implemented using ctypes, so no compilation is necessary.
0011"""
0012__all__ = ['authenticate']
0013
0014from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
0015from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
0016from ctypes.util import find_library
0017
0018LIBPAM = CDLL(find_library("pam"))
0019LIBC = CDLL(find_library("c"))
0020
0021CALLOC = LIBC.calloc
0022CALLOC.restype = c_void_p
0023CALLOC.argtypes = [c_uint, c_uint]
0024
0025STRDUP = LIBC.strdup
0026STRDUP.argstypes = [c_char_p]
0027STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
0028
0029# Various constants
0030PAM_PROMPT_ECHO_OFF = 1
0031PAM_PROMPT_ECHO_ON = 2
0032PAM_ERROR_MSG = 3
0033PAM_TEXT_INFO = 4
0034
0035class PamHandle(Structure):
0036    """wrapper class for pam_handle_t"""
0037    _fields_ = [
0038            ("handle", c_void_p)
0039            ]
0040
0041    def __init__(self):
0042        Structure.__init__(self)
0043        self.handle = 0
0044
0045class PamMessage(Structure):
0046    """wrapper class for pam_message structure"""
0047    _fields_ = [
0048            ("msg_style", c_int),
0049            ("msg", c_char_p),
0050            ]
0051
0052    def __repr__(self):
0053        return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
0054
0055class PamResponse(Structure):
0056    """wrapper class for pam_response structure"""
0057    _fields_ = [
0058            ("resp", c_char_p),
0059            ("resp_retcode", c_int),
0060            ]
0061
0062    def __repr__(self):
0063        return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
0064
0065CONV_FUNC = CFUNCTYPE(c_int,
0066        c_int, POINTER(POINTER(PamMessage)),
0067               POINTER(POINTER(PamResponse)), c_void_p)
0068
0069class PamConv(Structure):
0070    """wrapper class for pam_conv structure"""
0071    _fields_ = [
0072            ("conv", CONV_FUNC),
0073            ("appdata_ptr", c_void_p)
0074            ]
0075
0076PAM_START = LIBPAM.pam_start
0077PAM_START.restype = c_int
0078PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
0079        POINTER(PamHandle)]
0080
0081PAM_AUTHENTICATE = LIBPAM.pam_authenticate
0082PAM_AUTHENTICATE.restype = c_int
0083PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
0084
0085def authenticate(username, password, service='login'):
0086    """Returns True if the given username and password authenticate for the
0087    given service.  Returns False otherwise
0088    
0089    ``username``: the username to authenticate
0090    
0091    ``password``: the password in plain text
0092    
0093    ``service``: the PAM service to authenticate against.
0094                 Defaults to 'login'"""
0095    @CONV_FUNC
0096    def my_conv(n_messages, messages, p_response, app_data):
0097        """Simple conversation function that responds to any
0098        prompt where the echo is off with the supplied password"""
0099        # Create an array of n_messages response objects
0100        addr = CALLOC(n_messages, sizeof(PamResponse))
0101        p_response[0] = cast(addr, POINTER(PamResponse))
0102        for i in range(n_messages):
0103            if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
0104                pw_copy = STRDUP(str(password))
0105                p_response.contents[i].resp = cast(pw_copy, c_char_p)
0106                p_response.contents[i].resp_retcode = 0
0107        return 0
0108
0109    handle = PamHandle()
0110    conv = PamConv(my_conv, 0)
0111    retval = PAM_START(service, username, pointer(conv), pointer(handle))
0112
0113    if retval != 0:
0114        # TODO: This is not an authentication error, something
0115        # has gone wrong starting up PAM
0116        return False
0117
0118    retval = PAM_AUTHENTICATE(handle, 0)
0119    return retval == 0
0120
0121if __name__ == "__main__":
0122    import getpass
0123    print authenticate(getpass.getuser(), getpass.getpass())