I'm looking for a way to let my python program handle authentication through pam. I'm using http://code.google.com/p/web2py/source/browse/gluon/contrib/pam.py for this, which works out great as long as my python program runs as root which is not ideal to my opinion.
How can I make use of pam for username/password validation without requiring root privs?
short: use a proper Python PAM implementation, setup PAM properly.
long: In a sane PAM setup, you do not need root
privileges. In the end this is one of the things PAM provides, privilege separation.
pam_unix
has a way to check the password for you. Seems the PAM implementation of web2py
(note, it's from some contrib subdirectory...) is not doing the right thing. Maybe your PAM setup is not correct, which is hard to tell without further information; this also depends heavily on operating system and flavour/distribution.
There are multiple PAM bindings for Python out there (unfortunately nothing in the standard library), use these instead. And for configuration, there are tons of tutorials, find the right one for your system.
old/wrong, don't do this: You do not need to be root, you only need to be able to read /etc/shadow
. This file has usually group shadow
with read only access. So you simply need to add the user that is running the PAM
check in the shadow
group.
groupadd <user> shadow
should do the trick.
I think the pam
module is your best choice, but you don't have to embed it into your program directly. You could write a simple service which binds to a port on localhost, or listens on a UNIX domain socket, and fills PAM requests for other processes on the same host. Then have your web2py application connect to it for user/password validation.
For example:
import asyncore
import pam
import socket
class Client(asyncore.dispatcher_with_send):
def __init__(self, sock):
asyncore.dispatcher_with_send.__init__(self, sock)
self._buf = ''
def handle_read(self):
data = self._buf + self.recv(1024)
if not data:
self.close()
return
reqs, data = data.rsplit('\r\n', 1)
self._buf = data
for req in reqs.split('\r\n'):
try:
user, passwd = req.split()
except:
self.send('bad\r\n')
else:
if pam.authenticate(user, passwd):
self.send('ok\r\n')
else:
self.send('fail\r\n')
def handle_close(self):
self.close()
class Service(asyncore.dispatcher_with_send):
def __init__(self, addr):
asyncore.dispatcher_with_send.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(addr)
self.listen(1)
def handle_accept(self):
conn, _ = self.accept()
Client(conn)
def main():
addr = ('localhost', 8317)
Service(addr)
try:
asyncore.loop()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Usage:
% telnet localhost 8317
bob abc123
ok
larry badpass
fail
incomplete
bad
At the end I ended up using pexpect and trying to su - username. It's a bit slow, but it works pretty good. The below example isn't polished but you'll get the idea.
Cheers,
Jay
#!/usr/bin/python
import pexpect
def pam(username, password):
'''Accepts username and password and tried to use PAM for authentication'''
try:
child = pexpect.spawn('/bin/su - %s'%(username))
child.expect('Password:')
child.sendline(password)
result=child.expect(['su: Authentication failure',username])
child.close()
except Exception as err:
child.close()
print ("Error authenticating. Reason: "%(err))
return False
if result == 0:
print ("Authentication failed for user %s."%(username))
return False
else:
print ("Authentication succeeded for user %s."%(username))
return True
if __name__ == '__main__':
print pam(username='default',password='chandgeme')
Maybe python-pam can work for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With