FirefighterBlu3

PAM module for python3.2

Jan 9th, 2012
325
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.85 KB | None | 0 0
  1. # (c) 2007 Chris AtLee <chris@atlee.ca>
  2. # Licensed under the MIT license:
  3. # http://www.opensource.org/licenses/mit-license.php
  4. """
  5. PAM module for python
  6.  
  7. Provides an authenticate function that will allow the caller to authenticate
  8. a user against the Pluggable Authentication Modules (PAM) on the system.
  9.  
  10. Implemented using ctypes, so no compilation is necessary.
  11.  
  12. ---
  13. modified to work [better] with python3.2, 2011-12-6, david ford, <david@blue-labs.org>
  14. i haven't paid any attention to making sure things work in python2. there may be
  15. problems in my_conv()
  16.  
  17. """
  18.  
  19. import sys
  20. if sys.version_info >= (3,):
  21.     py3k = True
  22. else:
  23.     py3k = False
  24.  
  25. from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
  26. from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
  27. from ctypes import memmove
  28. from ctypes.util import find_library
  29.  
  30. class PamHandle(Structure):
  31.     """wrapper class for pam_handle_t"""
  32.     _fields_ = [
  33.             ("handle", c_void_p)
  34.             ]
  35.  
  36.     def __init__(self):
  37.         Structure.__init__(self)
  38.         self.handle = 0
  39.  
  40. class PamMessage(Structure):
  41.     """wrapper class for pam_message structure"""
  42.     _fields_ = [
  43.             ("msg_style", c_int),
  44.             ("msg", c_char_p),
  45.             ]
  46.  
  47.     def __repr__(self):
  48.         return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
  49.  
  50. class PamResponse(Structure):
  51.     """wrapper class for pam_response structure"""
  52.     _fields_ = [
  53.             ("resp", c_char_p),
  54.             ("resp_retcode", c_int),
  55.             ]
  56.  
  57.     def __repr__(self):
  58.         return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
  59.  
  60. CONV_FUNC = CFUNCTYPE(
  61.     c_int,
  62.     c_int,
  63.     POINTER(POINTER(PamMessage)),
  64.     POINTER(POINTER(PamResponse)),
  65.     c_void_p)
  66.  
  67. class PamConv(Structure):
  68.     """wrapper class for pam_conv structure"""
  69.     _fields_ = [
  70.             ("conv", CONV_FUNC),
  71.             ("appdata_ptr", c_void_p)
  72.             ]
  73.  
  74. # Various constants
  75. PAM_PROMPT_ECHO_OFF       = 1
  76. PAM_PROMPT_ECHO_ON        = 2
  77. PAM_ERROR_MSG             = 3
  78. PAM_TEXT_INFO             = 4
  79.  
  80. LIBC                      = CDLL(find_library("c"))
  81. LIBPAM                    = CDLL(find_library("pam"))
  82.  
  83. CALLOC                    = LIBC.calloc
  84. CALLOC.restype            = c_void_p
  85. CALLOC.argtypes           = [c_uint, c_uint]
  86.  
  87. PAM_START                 = LIBPAM.pam_start
  88. PAM_START.restype         = c_int
  89. PAM_START.argtypes        = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)]
  90.  
  91. PAM_STRERROR              = LIBPAM.pam_strerror
  92. PAM_STRERROR.restype      = c_char_p
  93. PAM_STRERROR.argtypes     = [POINTER(PamHandle), c_int]
  94.  
  95. PAM_AUTHENTICATE          = LIBPAM.pam_authenticate
  96. PAM_AUTHENTICATE.restype  = c_int
  97. PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
  98.  
  99. class pam():
  100.     code   = 0
  101.     reason = None
  102.  
  103.     def __init__(self):
  104.         pass
  105.  
  106.     def authenticate(self, username, password, service='login'):
  107.         """username and password authenticate for the given service.
  108.        
  109.           Returns True for success, or False.  self.code is the integer
  110.           value representing the numerice failure reason, or 0 on success.
  111.           self.reason is the textual reason.
  112.    
  113.           Python3 expects bytes() for ctypes inputs.  This function will make
  114.           necessary conversions using the latin-1 coding.
  115.        
  116.        username: the username to authenticate
  117.        password: the password in plain text
  118.         service: the PAM service to authenticate against.
  119.                  Defaults to 'login' """
  120.    
  121.         @CONV_FUNC
  122.         def my_conv(n_messages, messages, p_response, app_data):
  123.             """Simple conversation function that responds to any
  124.               prompt where the echo is off with the supplied password"""
  125.             # Create an array of n_messages response objects
  126.             addr = CALLOC(n_messages, sizeof(PamResponse))
  127.             p_response[0] = cast(addr, POINTER(PamResponse))
  128.             for i in range(n_messages):
  129.                 if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
  130.                     cs  = c_char_p(password)
  131.                     dst = CALLOC(sizeof(c_char_p), len(password)+1)
  132.                     memmove(dst , cs, len(password))
  133.                     p_response.contents[i].resp = dst
  134.                     p_response.contents[i].resp_retcode = 0
  135.             return 0
  136.    
  137.         # python3 ctypes prefers bytes, pretend everyone will be happy using latin-1
  138.         if py3k:
  139.             if isinstance(username, str):
  140.                 username = bytes(username, 'utf-8')
  141.             if isinstance(password, str):
  142.                 password = bytes(password, 'utf-8')
  143.             if isinstance(service, str):
  144.                 service  = bytes(service, 'utf-8')
  145.    
  146.         handle = PamHandle()
  147.         conv   = PamConv(my_conv, 0)
  148.         retval = PAM_START(service, username, pointer(conv), pointer(handle))
  149.    
  150.         if retval != 0:
  151.             # This is not an authentication error, something has gone wrong starting up PAM
  152.             self.code   = retval
  153.             self.reason = PAM_STRERROR(pointer(handle), retval)
  154.             return False
  155.    
  156.         retval = PAM_AUTHENTICATE(handle, 0)
  157.        
  158.         if retval == 0:
  159.             # success
  160.             logic = True
  161.         else:
  162.             logic = False
  163.        
  164.         # store information to inform the caller why we failed
  165.         self.code   = retval
  166.         self.reason = PAM_STRERROR(pointer(handle), retval)
  167.        
  168.         return logic
  169.    
  170.  
  171. if __name__ == "__main__":
  172.     pam = pam()
  173.  
  174.     if not pam.authenticate('david.ford','this would fail', service='wsgi'):
  175.         print (pam.code, pam.reason)
  176.     else:
  177.         print ('success')
  178.  
  179.     if not pam.authenticate('david.ford','arealpassword', service='wsgi'):
  180.         print(pam.code, pam.reason)
  181.     else:
  182.         print ('success')
Add Comment
Please, Sign In to add comment