Question

Currently I have a graphical application that has two levels of access, operator and administrator. The login and authentication is all homebrewed and I'd like to switch the application to use PAM instead. I'm not sure what the right way to do that is.

Correct me if I'm wrong, but it seems that PAM boils down to a "yes" or "no" check--yes you can access this service, or no you can't. There's no provision for having various levels of access based on which user is logging in. I need to be able to tell who's an operator and who's an admin, though, and I want to be able to do it strictly through PAM if possible.

So my thought is that I'd set up two services with two different configurations, /etc/pam.d/pamdemo for operators and /etc/pam.d/pamdemo-admin for administrators. My application would then try to authenticate against pamdemo-admin first, and if that fails then pamdemo. If both fails, access is denied. Am I on the right track or am I completely off the rails?

Here's some sample C code I've written up as a proof of concept. When I do the login I don't want to prompt the user for his credentials twice. I've got it so it remembers the username across the two pam_start() calls but I can't access pam_get_item(PAM_AUTHTOK) from the application level to do the same caching for the password. And it was in trying to do so that I realized that there might be a totally different way to do this. I would like this application to work no matter what the authentication method, be it username/password or Kerberos tickets or fingerprints, whatever.

pam_handle_t *try_login(const char *service, int *retval)
{
    static char *   username = NULL;
    struct pam_conv pam_conversation = { conv, NULL };
    pam_handle_t *  pamh;

    *retval = pam_start(service, username, &pam_conversation, &pamh);

    if (*retval == PAM_SUCCESS) *retval = pam_authenticate(pamh, 0);
    if (*retval == PAM_SUCCESS) *retval = pam_acct_mgmt   (pamh, 0);
    if (*retval == PAM_SUCCESS) *retval = pam_open_session(pamh, 0);

    if (username == NULL) {
        if (pam_get_item(pamh, PAM_USER, (const void **) &username) == PAM_SUCCESS) {
            username = strdup(username);
        }
    }

    if (*retval != PAM_SUCCESS) {
        fprintf(stderr, "%s: %s\n", service, pam_strerror(pamh, *retval));
        pam_end(pamh, *retval);
        pamh = NULL;
    }

    return pamh;
}

int main(void)
{
    pam_handle_t *pamh = NULL;
    int retval;
    const char *service, *username;

    if (!pamh) pamh = try_login("pamdemo-admin", &retval);
    if (!pamh) pamh = try_login("pamdemo",       &retval);

    if (!pamh) {
        fprintf(stderr, "Access denied.\n");
        return 1;
    }

    pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
    pam_get_item(pamh, PAM_USER,    (const void **) &username);

    printf("Logged into %s as %s.\n", service, username);

    pam_close_session(pamh, 0);
    pam_end          (pamh, retval);

    return 0;
}

As written this demo program repeats the "password:" prompt. I don't want it to ask twice!

Was it helpful?

Solution

I believe one right way to do this might be:

  • Set up the "pamdemo" service to do account, authentication and session functions.
  • Set up the "pamdemo-admin" service to only do account (and possibly session) functions. No authentication.
  • When logging in, first make them pass "pamdemo" (to ensure they are who they say they are) - if this fails, kick them out.
  • Then, once authenticated, hand them to "pamdemo-admin". This just checks to see if they're allowed to be admin - if they are, this check succeeds, if they aren't, it doesn't. Because this check doesn't do auth modules, they aren't prompted for a password again.

OTHER TIPS

Per caf's suggestion, here is my solution:

#define PAM_CALL(call)                               \
    do {                                             \
        if ((retval = (call)) != PAM_SUCCESS) {      \
            goto pam_error;                          \
        }                                            \
    } while (0)

int check_admin_login(const char *user)
{
    pam_handle_t *  pamh = NULL;
    struct pam_conv pam_conversation = { conv, NULL };
    int             retval;

    PAM_CALL(pam_start    ("pamdemo-admin", user, &pam_conversation, &pamh));
    PAM_CALL(pam_acct_mgmt(pamh, 0));
    PAM_CALL(pam_end      (pamh, retval));

    return 1;

pam_error:
    pam_end(pamh, retval);
    return 0;
}

int main(void)
{
    pam_handle_t *  pamh = NULL;
    struct pam_conv pam_conversation = { conv, NULL };
    int             retval;

    const char *    user;
    int             is_admin;

    PAM_CALL(pam_start        ("pamdemo", NULL, &pam_conversation, &pamh));
    PAM_CALL(pam_authenticate (pamh, 0));
    PAM_CALL(pam_acct_mgmt    (pamh, 0));
    PAM_CALL(pam_open_session (pamh, 0));
    PAM_CALL(pam_get_item     (pamh, PAM_USER, (const void **) &user));

    is_admin = check_admin_login(user);
    printf("Logged in as %s (%s).\n", user, is_admin ? "administrator" : "operator");

    PAM_CALL(pam_close_session(pamh, 0));
    pam_end (pamh, retval);

    return 0;

pam_error:
    fprintf(stderr, "%s\n", pam_strerror(pamh, retval));
    pam_end(pamh, retval);

    return 1;
}

you can just use the command "groups " or "id " and get the groups for the user, then grep the groups and if you hit admin first, then it's an admin user otherwise it's a demo user.

The groups / id commands (tested on Linux) will get the groups for non-local users as well (e.g PAM / LDAP)

So, instead of checking against a service, check the group in which the user belongs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top