PAM Authentication for a Legacy Application

The way standard information (such as a password) is passed for PAM is by using variables set in the pam handle with pam_set_item (see the man page for pam_set_item).

You can set anything your application will need to use later into the pam_stack. If you want to put the password into the pam_stack you should be able to do that immediately after calling pam_start() by setting the PAM_AUTHTOK variable into the stack similar to the pseudo code below:

pam_handle_t* handle = NULL;
pam_start("common-auth", username, NULL, &handle);
pam_set_item( handle, PAM_AUTHTOK, password);

This will make the password available on the stack to any module that cares to use it, but you generally have to tell the module to use it by setting the standard use_first_pass, or try_first_pass options in the pam_configuration for the service (in this case /etc/pam.d/common-auth).

The standard pam_unix module does support try_first_pass, so it wouldn't hurt to add that into your pam configuration on your system (at the end of the line for pam_unix).

After you do this any call to pam_authenticate() that are invoked from the common-auth service should just pick the password up and go with it.

One small note about the difference between use_first_pass and try_first_pass: They both tell the module (in this case pam_unix) to try the password on the pam_stack, but they differ in behavior when their is no password/AUTHTOK available. In the missing case use_first_pass fails, and try_first_pass allows the module to prompt for a password.


This is what I ended up doing. See the comment marked with three asterisks.

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <security/pam_appl.h>
#include <unistd.h>

// To build this:
// g++ test.cpp -lpam -o test

// if pam header files missing try:
// sudo apt install libpam0g-dev

struct pam_response *reply;

//function used to get user input
int function_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
  *resp = reply;
  return PAM_SUCCESS;
}

int main(int argc, char** argv)
{
  if(argc != 2) {
      fprintf(stderr, "Usage: check_user <username>\n");
      exit(1);
  }
  const char *username;
  username = argv[1];

  const struct pam_conv local_conversation = { function_conversation, NULL };
  pam_handle_t *local_auth_handle = NULL; // this gets set by pam_start

  int retval;

  // local_auth_handle gets set based on the service
  retval = pam_start("common-auth", username, &local_conversation, &local_auth_handle);

  if (retval != PAM_SUCCESS)
  {
    std::cout << "pam_start returned " << retval << std::endl;
    exit(retval);
  }

  reply = (struct pam_response *)malloc(sizeof(struct pam_response));

  // *** Get the password by any method, or maybe it was passed into this function.
  reply[0].resp = getpass("Password: ");
  reply[0].resp_retcode = 0;

  retval = pam_authenticate(local_auth_handle, 0);

  if (retval != PAM_SUCCESS)
  {
    if (retval == PAM_AUTH_ERR)
    {
      std::cout << "Authentication failure." << std::endl;
    }
    else
    {
      std::cout << "pam_authenticate returned " << retval << std::endl;
    }
    exit(retval);
  }

  std::cout << "Authenticated." << std::endl;

  retval = pam_end(local_auth_handle, retval);

  if (retval != PAM_SUCCESS)
  {
    std::cout << "pam_end returned " << retval << std::endl;
    exit(retval);
  }

  return retval;
}