Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pam authentication in python without root privileges

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?

like image 297
jay_t Avatar asked Mar 12 '11 23:03

jay_t


4 Answers

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.

like image 64
trapicki Avatar answered Oct 20 '22 19:10

trapicki


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
like image 24
samplebias Avatar answered Oct 20 '22 18:10

samplebias


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')
like image 4
jay_t Avatar answered Oct 20 '22 19:10

jay_t


Maybe python-pam can work for you.

like image 3
Tobias Kienzler Avatar answered Oct 20 '22 19:10

Tobias Kienzler