Question

I have a legacy app that receives a username/password request asynchronously over the wire. Since I already have the username and password stored as variables, what would be the best way to authenticate with PAM on Linux (Debian 6)?

I've tried writing my own conversation function, but I'm not sure of the best way of getting the password into it. I've considered storing it in appdata and referencing that from the pam_conv struct, but there's almost no documentation on how to do that.

Is there a simpler way to authenticate users without the overkill of a conversation function? I'm unable to use pam_set_data successfully either, and I'm not sure that's even appropriate.

Here's what I'm doing:

user = guiMessage->username;
pass = guiMessage->password;

pam_handle_t* pamh = NULL;
int           pam_ret;
struct pam_conv conv = {
  my_conv,
  NULL
};

pam_start("nxs_login", user, &conv, &pamh);
pam_ret = pam_authenticate(pamh, 0);

if (pam_ret == PAM_SUCCESS)
  permissions = 0xff;

pam_end(pamh, pam_ret);

And initial attempts at the conversation function resulted in (password is hard-coded for testing):

int 
my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *data)
{
  struct pam_response *aresp;

  if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
    return (PAM_CONV_ERR);
  if ((aresp = (pam_response*)calloc(num_msg, sizeof *aresp)) == NULL)
    return (PAM_BUF_ERR);
  aresp[0].resp_retcode = 0;
  aresp[0].resp = strdup("mypassword");

  *resp = aresp;
  return (PAM_SUCCESS);
}

Any help would be appreciated. Thank you!

Was it helpful?

Solution

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;
}

OTHER TIPS

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.

Fantius' solution worked for me, even as root.

I originally opted for John's solution, as it was cleaner and made use of PAM variables without the conversation function (really, there isn't a need for it here), but it did not, and will not, work. As Adam Badura alluded to in both posts, PAM has some internal checks to prevent direct setting of PAM_AUTHTOK.

John's solution will result in behaviour similar to what is mentioned here, where any password value will be allowed to login (even if you declare, but do not define, the pam_conv variable).

I would also recommend users be aware of the placement of the malloc, as it will likely differ in your application (remember, the code above is more of a test/template, than anything else).

struct pam_conv {
    int (*conv)(int num_msg, const struct pam_message **msg,
                struct pam_response **resp, void *appdata_ptr);
    void *appdata_ptr;
};

The second field(appdata_ptr) of the struct pam_conv is passed to the conversation function, therefore we can use it as our password pointer.

     static int convCallback(int num_msg, const struct pam_message** msg,
                             struct pam_response** resp, void* appdata_ptr)
     {
            struct pam_response* aresp;
        
            if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
                return (PAM_CONV_ERR);
            if ((aresp = (pam_response*)calloc(num_msg, sizeof * aresp)) == NULL)
                return (PAM_BUF_ERR);
            aresp[0].resp_retcode = 0;
            aresp[0].resp = strdup((char*)appdata_ptr);
                                                    
            *resp = aresp;
            
            return (PAM_SUCCESS);
    }

    int main()
    {
        ....
        pam_handle_t* pamH = 0;
        char *password = strdup("foopassword");
        struct pam_conv conversation = {convCallback, password};
        int retvalPam = pam_start("check_user", "foousername", &conversation, &pamH);
        
        //Call pam_authenticate(pamH, 0)
        //Call pam_end(pamH, 0);
        ...
        ...
        free(password);
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top