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 | from __future__ import print_function |
---|
13 | __all__ = ['authenticate'] |
---|
14 | |
---|
15 | from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof |
---|
16 | from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int |
---|
17 | from ctypes.util import find_library |
---|
18 | |
---|
19 | LIBPAM = CDLL(find_library("pam")) |
---|
20 | LIBC = CDLL(find_library("c")) |
---|
21 | |
---|
22 | CALLOC = LIBC.calloc |
---|
23 | CALLOC.restype = c_void_p |
---|
24 | CALLOC.argtypes = [c_uint, c_uint] |
---|
25 | |
---|
26 | STRDUP = LIBC.strdup |
---|
27 | STRDUP.argstypes = [c_char_p] |
---|
28 | STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!! |
---|
29 | |
---|
30 | # Various constants |
---|
31 | PAM_PROMPT_ECHO_OFF = 1 |
---|
32 | PAM_PROMPT_ECHO_ON = 2 |
---|
33 | PAM_ERROR_MSG = 3 |
---|
34 | PAM_TEXT_INFO = 4 |
---|
35 | |
---|
36 | |
---|
37 | class PamHandle(Structure): |
---|
38 | """wrapper class for pam_handle_t""" |
---|
39 | _fields_ = [ |
---|
40 | ("handle", c_void_p) |
---|
41 | ] |
---|
42 | |
---|
43 | def __init__(self): |
---|
44 | Structure.__init__(self) |
---|
45 | self.handle = 0 |
---|
46 | |
---|
47 | |
---|
48 | class PamMessage(Structure): |
---|
49 | """wrapper class for pam_message structure""" |
---|
50 | _fields_ = [ |
---|
51 | ("msg_style", c_int), |
---|
52 | ("msg", c_char_p), |
---|
53 | ] |
---|
54 | |
---|
55 | def __repr__(self): |
---|
56 | return "<PamMessage %i '%s'>" % (self.msg_style, self.msg) |
---|
57 | |
---|
58 | |
---|
59 | class PamResponse(Structure): |
---|
60 | """wrapper class for pam_response structure""" |
---|
61 | _fields_ = [ |
---|
62 | ("resp", c_char_p), |
---|
63 | ("resp_retcode", c_int), |
---|
64 | ] |
---|
65 | |
---|
66 | def __repr__(self): |
---|
67 | return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp) |
---|
68 | |
---|
69 | CONV_FUNC = CFUNCTYPE(c_int, |
---|
70 | c_int, POINTER(POINTER(PamMessage)), |
---|
71 | POINTER(POINTER(PamResponse)), c_void_p) |
---|
72 | |
---|
73 | |
---|
74 | class PamConv(Structure): |
---|
75 | """wrapper class for pam_conv structure""" |
---|
76 | _fields_ = [ |
---|
77 | ("conv", CONV_FUNC), |
---|
78 | ("appdata_ptr", c_void_p) |
---|
79 | ] |
---|
80 | |
---|
81 | PAM_START = LIBPAM.pam_start |
---|
82 | PAM_START.restype = c_int |
---|
83 | PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv), |
---|
84 | POINTER(PamHandle)] |
---|
85 | |
---|
86 | PAM_AUTHENTICATE = LIBPAM.pam_authenticate |
---|
87 | PAM_AUTHENTICATE.restype = c_int |
---|
88 | PAM_AUTHENTICATE.argtypes = [PamHandle, c_int] |
---|
89 | |
---|
90 | |
---|
91 | def authenticate(username, password, service='login'): |
---|
92 | """Returns True if the given username and password authenticate for the |
---|
93 | given service. Returns False otherwise |
---|
94 | |
---|
95 | ``username``: the username to authenticate |
---|
96 | |
---|
97 | ``password``: the password in plain text |
---|
98 | |
---|
99 | ``service``: the PAM service to authenticate against. |
---|
100 | Defaults to 'login'""" |
---|
101 | @CONV_FUNC |
---|
102 | def my_conv(n_messages, messages, p_response, app_data): |
---|
103 | """Simple conversation function that responds to any |
---|
104 | prompt where the echo is off with the supplied password""" |
---|
105 | # Create an array of n_messages response objects |
---|
106 | addr = CALLOC(n_messages, sizeof(PamResponse)) |
---|
107 | p_response[0] = cast(addr, POINTER(PamResponse)) |
---|
108 | for i in range(n_messages): |
---|
109 | if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: |
---|
110 | pw_copy = STRDUP(str(password)) |
---|
111 | p_response.contents[i].resp = cast(pw_copy, c_char_p) |
---|
112 | p_response.contents[i].resp_retcode = 0 |
---|
113 | return 0 |
---|
114 | |
---|
115 | handle = PamHandle() |
---|
116 | conv = PamConv(my_conv, 0) |
---|
117 | retval = PAM_START(service, username, pointer(conv), pointer(handle)) |
---|
118 | |
---|
119 | if retval != 0: |
---|
120 | # TODO: This is not an authentication error, something |
---|
121 | # has gone wrong starting up PAM |
---|
122 | return False |
---|
123 | |
---|
124 | retval = PAM_AUTHENTICATE(handle, 0) |
---|
125 | return retval == 0 |
---|
126 | |
---|
127 | if __name__ == "__main__": |
---|
128 | import getpass |
---|
129 | print(authenticate(getpass.getuser(), getpass.getpass())) |
---|