Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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="<group_name>"``,
this will automatically add them to the group.
Empty file removed dpam/__init__.py
Empty file.
22 changes: 18 additions & 4 deletions dpam/backends.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
37 changes: 20 additions & 17 deletions dpam/pam.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,59 +24,63 @@

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
PAM_PROMPT_ECHO_ON = 2
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 "<PamMessage %i '%s'>" % (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 "<PamResponse %i '%s'>" % (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
Expand All @@ -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

Expand Down
34 changes: 34 additions & 0 deletions dpam/shadow.py
Original file line number Diff line number Diff line change
@@ -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