views:

107

answers:

2

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!

+1  A: 

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.
caf
A: 

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