Help with PAM and ctypes

Chris AtLee chris.atlee at gmail.com
Thu Jun 14 08:46:18 EDT 2007


On Jun 11, 6:01 pm, Lenard Lindstrom <l... at telus.net> wrote:
[snip snip snip]
> > if __name__ == "__main__":
> >     import getpass, os, sys
> >     @conv_func
> >     def my_conv(nMessages, messages, pResponse, appData):
> >         # Create an array of nMessages response objects
> >         # Does r get GC'ed after we're all done?
> >         r = (pam_response * nMessages)()
>
> The memory allocated to r is garbage collected immediately after my_conv
> returns. You need to allocate it explicitly using C's calloc or such.
> This assumes pam_start will free the memory for you.
>
> addr = calloc(sizeof(pam_response), nMessages)
>
> addr is the memory address as a Python integer.
>
> >         pResponse.contents = cast(r, POINTER(pam_response))
>
> pResponse.contents changes the actual value of pResponse, a value on the
> stack. You want to change the value of the pointer pResponse points to:
>
> pResponse[0] = cast(addr, POINTER(pam_response))
>
> The cast creates a POINTER(pam_reponse) instance with value addr.

Ahhh, thank you!  I never understood how ctypes' pointer.contents
related
to C pointers.  So, the following are equivalent?

int  v = 42;        v = 42
int *p = 0;         p = cast(0, POINTER(c_int))
p = &v;             p.contents = v
*p = 123;           p[0] = 123

Using "pResponse[0] = cast(...)" got me part of the way to fixing my
problem.  PAM started either crashing or saying authentication had
failed.
The crash was in free(), and some digging around revealed that PAM
tries to
free the response sent by the application, which makes sense.

My initial attempt to fix this involved wrapping strdup to allocate a
new
copy of a string to send back to PAM.  Here's how I wrapped it:

strdup = libc.strdup
strdup.argstypes = [c_char_p]
strdup.restype = c_char_p

This still crashed in free().  I took a look at some of the ctypes
regression tests and something made me try this:

strdup = libc.strdup
strdup.argstypes = [c_char_p]
strdup.restype = POINTER(c_char) # NOT c_char_p !!!!

This works like a charm.  Not sure why though...Does ctypes do
something
special with c_char_p return types?  It seems as if Python was freeing
the
result of strdup() when the string was GC'ed, and then PAM would fail
when
trying to free the same memory.

My working code is pasted below.

Thanks,
Chris

from ctypes import *

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 pam_handle(Structure):
    _fields_ = [
            ("handle", c_void_p)
            ]

    def __init__(self):
        self.handle = 0

class pam_message(Structure):
    _fields_ = [
            ("msg_style", c_int),
            ("msg", c_char_p),
            ]

    def __repr__(self):
        return "<pam_message %i '%s'>" % (self.msg_style, self.msg)

class pam_response(Structure):
    _fields_ = [
            ("resp", c_char_p),
            ("resp_retcode", c_int),
            ]

    def __repr__(self):
        return "<pam_response %i '%s'>" % (self.resp_retcode,
self.resp)

conv_func = CFUNCTYPE(c_int,
        c_int, POINTER(POINTER(pam_message)),
               POINTER(POINTER(pam_response)), c_void_p)

class 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(pam_conv),
POINTER(pam_handle)]

pam_authenticate = libpam.pam_authenticate
pam_authenticate.restype = c_int
pam_authenticate.argtypes = [pam_handle, c_int]

if __name__ == "__main__":
    import getpass, os, sys
    @conv_func
    def my_conv(nMessages, messages, pResponse, appData):
        # Create an array of nMessages response objects
        addr = calloc(nMessages, sizeof(pam_response))
        pResponse[0] = cast(addr, POINTER(pam_response))
        for i in range(nMessages):
            if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
                p = strdup(getpass.getpass(messages[i].contents.msg))
                pResponse.contents[i].resp = cast(p, c_char_p)
                pResponse.contents[i].resp_retcode = 0
            else:
                print "Unknown message type"
        return 0

    handle = pam_handle()
    c = pam_conv(my_conv, 0)
    retval = pam_start("login", getpass.getuser(), pointer(c),
pointer(handle))

    if retval != 0:
        print "Couldn't start pam session"
        sys.exit(-1)

    retval = pam_authenticate(handle, 0)
    if retval == 21:
        print "Authentication information cannot be recovered"
        sys.exit(-1)
    elif retval == 7:
        print "Authentication failure"
    elif retval == 0:
        print "Ok!"
    else:
        print retval




More information about the Python-list mailing list