How to Create a Pam Module

Simple PAM example

First off, sufficient will still fail if a previous required module has failed. Since you say you have put your sufficient line beneath the include of common-auth you may be seeing a failure because some required module in common-auth has denied access already. Plus you have have sshd getting in the way.

I'd get all this stuff out of the way so you know your test is really a test of your pam module and not some further interaction with other things. I'd start with a simple test program like the one here with /etc/pam.d/check_user listing your module instead of pam_unix.

Where to put a PAM module?

For those who want to create a PAM module, there is a very good example here: https://github.com/beatgammit/simple-pam.

In my case, the functions to call in my PAM module to exec something at login/logout are:

PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int  flags,int argc, const char **argv ) {
printf("Connected\n");
return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,int argc, const char **argv ) {
printf("Disconnected\n");
return PAM_SUCCESS;
}

As indicated in the README, the so file needs to be put into /lib/security/.

Writing PAM Module that Will Read Password From File and Log the User In

The answer is here:

  1. Write your custom pam module. (Tons of examples can be found on internet)
  2. Read data as you wish. (read from file or smthing else with any function you may like)
  3. Compare hashed passwords as it shown below and here:
if ((!pwd->pw_passwd[0] && (flags & PAM_DISALLOW_NULL_AUTHTOK)) ||
(crypt_password = crypt(password, pwd->pw_passwd)) == NULL ||
strcmp(crypt_password, pwd->pw_passwd) != 0)
pam_err = PAM_AUTH_ERR;

if match, return success.

That is it.

Building a PAM module and install into /lib/security

To address your two things in order:

  • The libtool script intercepts that install instruction and also installs the .so file in the appropriate place.

  • It's putting it in /usr/local/lib probably because you listed it in lib_LTLIBRARIES (although I can't be sure if you don't show your code) and your --prefix is set to its default of /usr/local.

This last one is difficult since Autotools' official stance is that all user-installed programs belong in /usr, whereas many other tools expect things to be in /lib/something. Here's one way to do it, that I personally consider wrong:

# Don't do this
libsecuritydir = /lib/security
libsecurity_LTLIBRARIES = pam_mymodule.la

This bypasses --prefix, which will go horribly, horribly wrong if you try to do a local install of your package without writing directly into your live system, which, trust me, you will want to do at some point. It will also prevent you from packaging your program in most Linux distributions' packaging systems.

The correct way is to push the responsibility onto whoever installs the package: add a --with-libsecuritydir argument to configure.ac using AC_ARG_WITH and let that default to $(libdir)/security:

AC_ARG_WITH([libsecuritydir],
[AS_HELP_STRING([--with-libsecuritydir],
[Directory for PAM modules, pass /lib/security for live install])],
[], [with_libsecuritydir='$(libdir)/security'])
AC_SUBST([libsecuritydir], [$with_libsecuritydir])

and then just do

libsecurity_LTLIBRARIES = pam_mymodule.la

in Makefile.am.

When you want to install a live version directly into your system (or are building a binary package) pass --with-libsecuritydir=/lib/security to configure.

Linux PAM module in Java

You could try:

  • Compile your Java program using GCJ to native code
  • Write glue C program which embeds JVM and loads your Java code

but neither of those ideas seem ideal.

Custom PAM for SSHD in C

You cannot use scanf() to read a value from the user, since stdin is not passed to the user. You have to use ssh keyboard-interactive authentication with the appropriate functions. This is supported by the pam_conv (PAM conversation) functions.

The manpage _man 3 pam_conv_ describes the interface. It works basically like this:

PAM_EXTERN int
pam_sm_authenticate (pam_handle_t * pamh,
int flags, int argc, const char **argv)
{
struct pam_message msg[1];
struct pam_response *resp = NULL;
struct pam_conv *conv;

pam_get_item (pamh, PAM_CONV, (const void **) &conv);
msg[0].msg = "Enter your secret token: ";
msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
conv->conv(1, &msg, &resp, conv->appdata_ptr);
// Token is now in resp->resp;
....
}

You can use this as a start for your custom authentication function.

Architecture Direction Help: Creating an Azure Active Directory PAM Module

bureado's PAM you point to uses what's known as the OAuth "Resource Owner Password Credentials Grant". It basically takes the user's username & password and passes them to Azure AD for authentication. It has a bunch of limitations, several of which Vittorio describes here. A core problem you pointed out is that MFA does not work.

For scenarios like this Azure AD also supports the OAuth "Device Profile Flow". There's a code sample here that shows how to do it in .NET: https://github.com/Azure-Samples/active-directory-dotnet-deviceprofile. I'd recommend going that route.



Related Topics



Leave a reply



Submit