diff --git a/README.rst b/README.rst index 3fca312..ef911c4 100644 --- a/README.rst +++ b/README.rst @@ -19,3 +19,6 @@ object is created. By default this new ``User`` has both ``is_staff`` and If you do not want a ``User`` record to be created automatically, use ``PAM_CREATE_USER=False`` in ``settings.py``. This is useful in situations where you want to use PAM for authentication but not for authorization. + +If you want your new users in a specific group use the setting ``PAM_USERS_GROUP=""``, +this will automatically add them to the group. diff --git a/dpam/__init__.py b/dpam/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/dpam/backends.py b/dpam/backends.py index 4ad04fe..3e612e4 100644 --- a/dpam/backends.py +++ b/dpam/backends.py @@ -1,30 +1,44 @@ -import pam +""" Django PAM Authentication Backend""" +import syslog from django.conf import settings from django.contrib.auth.models import User +from django.contrib.auth.models import Group from django.contrib.auth.backends import ModelBackend +import dpam.pam as pam + + class PAMBackend(ModelBackend): + """PAM Auth Backend""" + def authenticate(self, username=None, password=None): + syslog.syslog('django pam realyy') service = getattr(settings, 'PAM_SERVICE', 'login') if not pam.authenticate(username, password, service=service): return None try: user = User.objects.get(username=username) - except: + except User.DoesNotExist: if not getattr(settings, "PAM_CREATE_USER", True): return None user = User(username=username, password='not stored here') user.set_unusable_password() if getattr(settings, 'PAM_IS_SUPERUSER', False): - user.is_superuser = True + user.is_superuser = True if getattr(settings, 'PAM_IS_STAFF', user.is_superuser): - user.is_staff = True + user.is_staff = True user.save() + + if getattr(settings, 'PAM_USERS_GROUP', False): + group = Group.objects.get(name=settings.PAM_USERS_GROUP) + group.user_set.add(user) + group.save() + return user def get_user(self, user_id): diff --git a/dpam/pam.py b/dpam/pam.py index a799f07..d3284e7 100644 --- a/dpam/pam.py +++ b/dpam/pam.py @@ -24,7 +24,7 @@ STRDUP = LIBC.strdup STRDUP.argstypes = [c_char_p] -STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!! +STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!! # Various constants PAM_PROMPT_ECHO_OFF = 1 @@ -32,51 +32,55 @@ PAM_ERROR_MSG = 3 PAM_TEXT_INFO = 4 + class PamHandle(Structure): """wrapper class for pam_handle_t""" _fields_ = [ - ("handle", c_void_p) - ] + ("handle", c_void_p) + ] def __init__(self): Structure.__init__(self) self.handle = 0 + class PamMessage(Structure): """wrapper class for pam_message structure""" _fields_ = [ - ("msg_style", c_int), - ("msg", c_char_p), - ] + ("msg_style", c_int), + ("msg", c_char_p), + ] def __repr__(self): return "" % (self.msg_style, self.msg) + class PamResponse(Structure): """wrapper class for pam_response structure""" _fields_ = [ - ("resp", c_char_p), - ("resp_retcode", c_int), - ] + ("resp", c_char_p), + ("resp_retcode", c_int), + ] def __repr__(self): return "" % (self.resp_retcode, self.resp) CONV_FUNC = CFUNCTYPE(c_int, - c_int, POINTER(POINTER(PamMessage)), - POINTER(POINTER(PamResponse)), c_void_p) + c_int, POINTER(POINTER(PamMessage)), + POINTER(POINTER(PamResponse)), c_void_p) + class PamConv(Structure): """wrapper class for pam_conv structure""" _fields_ = [ - ("conv", CONV_FUNC), - ("appdata_ptr", c_void_p) - ] + ("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(PamConv), - POINTER(PamHandle)] + POINTER(PamHandle)] PAM_AUTHENTICATE = LIBPAM.pam_authenticate PAM_AUTHENTICATE.restype = c_int @@ -91,8 +95,7 @@ class PamConv(Structure): PAM_END.argtypes = [PamHandle, c_int] - -def authenticate(username, password, service='login', enable_acct_mgrt=False): +def authenticate(username, password, service='login', enable_acct_mgrt=False): """Returns True if the given username and password authenticate for the given service. Returns False otherwise diff --git a/dpam/shadow.py b/dpam/shadow.py new file mode 100644 index 0000000..4c89641 --- /dev/null +++ b/dpam/shadow.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +from os import path +from crypt import crypt +from re import compile as compile_regex + + +def check_auth(user, password): + """Perform authentication against the local systme. + + This function will perform authentication against the local system's + /etc/shadow or /etc/passwd database for a given user and password. + + :param user: The username to perform authentication with + :type user: str + + :param password: The password (plain text) for the given user + :type password: str + + :returns: True if successful, None otherwise. + :rtype: True or None + """ + + salt_pattern = compile_regex(r"\$.*\$.*\$") + passwd = "/etc/shadow" if path.exists("/etc/shadow") else "/etc/passwd" + + with open(passwd, "r") as f: + rows = (line.strip().split(":") for line in f) + records = [row for row in rows if row[0] == user] + + hash = records and records[0][1] + salt = salt_pattern.match(hash).group() + + return crypt(password, salt) == hash