473,385 Members | 1,712 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,385 software developers and data experts.

Help with PAM and ctypes

Sorry for the repeat post...I'm not sure if my first post (on May
30th) went through or
not.

I've been trying to write a PAM module using ctypes. In the
conversation
function (my_conv in the script below), you're passed in a
pam_response**
pointer. You're supposed to allocate an array of pam_response's and
set
the pointer's value to the new array. Then you fill in the array with
appropriate data.

I can't seem to get it working in python...The authenticate function
always
returns PAM_AUTHTOK_RECOVER_ERR (21), which I think means the response
doesn't make any sense.

I've tried saving the response array outside of my_conv to make sure
it
doesn't get garbage collected, but that doesn't seem to help.

Any pointers would be appreciated!

Cheers,
Chris

from ctypes import *

libpam = CDLL("libpam.so")

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
# Does r get GC'ed after we're all done?
r = (pam_response * nMessages)()
pResponse.contents = cast(r, POINTER(pam_response))
for i in range(nMessages):
if messages[i].contents.msg == "Password: ":
p = getpass.getpass()
pResponse.contents[0].resp_retcode = 0
pResponse.contents[0].resp = p
return 0

handle = pam_handle()
c = pam_conv(my_conv, 0)
retval = pam_start("login", os.getlogin(), 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)

print retval

Jun 7 '07 #1
3 2634
Chris AtLee wrote:
Sorry for the repeat post...I'm not sure if my first post (on May
30th) went through or
not.

I've been trying to write a PAM module using ctypes. In the
conversation
function (my_conv in the script below), you're passed in a
pam_response**
pointer. You're supposed to allocate an array of pam_response's and
set
the pointer's value to the new array. Then you fill in the array with
appropriate data.

I can't seem to get it working in python...The authenticate function
always
returns PAM_AUTHTOK_RECOVER_ERR (21), which I think means the response
doesn't make any sense.

I've tried saving the response array outside of my_conv to make sure
it
doesn't get garbage collected, but that doesn't seem to help.

Any pointers would be appreciated!

Cheers,
Chris
[snip some code]
>
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
# 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.
for i in range(nMessages):
if messages[i].contents.msg == "Password: ":
p = getpass.getpass()
pResponse.contents[0].resp_retcode = 0
pResponse.contents[0].resp = p
return 0

handle = pam_handle()
c = pam_conv(my_conv, 0)
retval = pam_start("login", os.getlogin(), 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)

print retval
If you are going to do any serious ctypes coding consider joining the
ctypes mailing list at sourceforge.net .
--
Lenard Lindstrom
<le***@telus.net>
Jun 11 '07 #2
On Jun 11, 6:01 pm, Lenard Lindstrom <l...@telus.netwrote:
[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

Jun 14 '07 #3
Chris AtLee wrote:
On Jun 11, 6:01 pm, Lenard Lindstrom <l...@telus.netwrote:
[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
Actually

int v = 42; v = c_int(42)

for p.contents = v to work. 42 is a Python integer. c_int(42) refers to
a mutable C integer in memory initialized to 42. Also, the preferred way
to create a NULL pointer in ctypes is to call the pointer type without
an argument:

int *p = 0; p = POINTER(c_int)()

>
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.
c_char_p implicitly converts to/from a Python string. So strdup with a
c_char_p restype returns a copy of the returned C string as a Python
string instead of a pointer to the memory allocated by strdup. And I
don't know how a Python string is cast to a c_char_p.

--
Lenard Lindstrom
<le***@telus.net>
Jun 15 '07 #4

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

1
by: Thomas Heller | last post by:
ctypes 0.9.1 released - Sept 14, 2004 ===================================== Overview ctypes is a ffi (Foreign Function Interface) package for Python 2.3 and higher. ctypes allows to call...
19
by: Thomas Heller | last post by:
ctypes 0.9.2 released - Oct 28, 2004 ==================================== Overview ctypes is a ffi (Foreign Function Interface) package for Python 2.3 and higher. ctypes allows to call...
2
by: gap | last post by:
I'm no c programmer, and I'm a ctypes newbie. I'll frame my problem as simply as I can. Sorry if it's too much or not enough info. I don't expect an explicit answer (but maybe), just help...
1
by: sjdevnull | last post by:
Hey, I'm trying to wrap GNU readline with ctypes (the Python readline library doesn't support the callback interface), but I can't figure out how to set values to a variable inside the library. ...
7
by: p.lavarre | last post by:
How do I vary the byte offset of a field of a ctypes.Structure? How do I "use the dynamic nature of Python, and (re-)define the data type after the required size is already known, on a case by...
6
by: Jack | last post by:
I'm not able to build IP2Location's Python interface so I'm trying to use ctypes to call its C interface. The functions return a pointer to the struct below. I haven't been able to figure out how...
8
by: gianluca | last post by:
Hy, I need help about use dll with ctypes. I've compiled my dll C library and I copied it in c:\windows\system32. When I load it with "myDLL=cdll.LoadLibrary(find_library("myDLL.dll"))" It seem...
0
by: Egor Zindy | last post by:
Egor Zindy wrote: #!/usr/bin/env python """ A generic chipid library based on ctypes This module handles most of the functions in FTChipID.dll
3
by: Andrew Lentvorski | last post by:
Basically, I'd like to use the ctypes module as a much more descriptive "struct" module. Is there a way to take a ctypes.Structure-based class and convert it to/from a binary string? Thanks,...
2
by: Sells, Fred | last post by:
Diez wrote... You're right the ctypes does seem more pythonesque; however I'm still stuck trying return all these parameters that the c api uses. my ctypes code is below. It just quits running...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.