2

Is there a way of properly authenticating a user from within a custom PAM session?

I am currently writing my own PAM authentication module which allows the user to login via an external token. This token has to be generated before the user can login with my module. Therefore I would like to fall back to default PAM authentication when no token exists and continue with my code as soon as the user is authenticated.

Is this possible somehow? In pseudo-code my module looks like this:

pam_sm_authenticate() {
  if (first_login) {
    code_copied_from_pam_unix_to_authenticate_user();
    // do something else here?
  } else {
    custom_auth();
  }
}

As a quick fix, I copied the code from Linux's pam_unix module into my own and it works. However, this is not very satisfactory as it incurs a lot of additional libraries and also will only work as long as pam_unix doesn't change. I would prefer opening another PAM session within my session but haven't got it to work.

2

Don't let your code carry out all the logic: use PAM and its configuration first to make sure your module runs in the best conditions (ie. does not require copying pam_unix's code).

First, let me suggest another pseudo code for your module:

pam_sm_authenticate() {
    if (first_login) return PAM_CRED_INSUFFICIENT;
    else custom_auth();
}

Here, I consider first login to be a case of credentials insufficiency. I'm telling PAM that the module is failing because it doesn't have everything it needs to fully authenticate the user. Now, assuming your module is called my_module, a possible configuration would be:

auth [cred_insufficient=ok success=done default=2] my_module.so
auth [success=ok default=1]      pam_unix.so
auth sufficient                  my_module.so
auth requisite                   pam_deny.so

Here are the details:

  • First, the request goes through my_module. Several possibilities here:

    1. First login: your module returned PAM_CRED_INSUFFICIENT. This case is caught by PAM (through cred_insufficient), in which case it is configured to mark the chain as successful (ok) but to keep going.
    2. This wasn't the first login, you went through custom_auth() and it was a success (it returned PAM_SUCCESS). In this case, we put an end to the chain (done) : access granted.
    3. This wasn't the first login, and custom_auth() didn't end up well (PAM_AUTH_ERR or other kinds of internal errors). In this case, skip the next 2 lines (default=2). The chain goes right into pam_deny, which always fails: access denied.
  • In the first scenario, the chain goes on to pam_unix. Two possibilities here:

    1. UNIX authentication succeeds. This marks the chain as successful (ok) and goes on to the next module.
    2. UNIX authentication fails. The next module is skipped (default=1), and the chain ends in pam_deny: access denied.
  • If you reach the third line, it means that my_module ended with PAM_CRED_INSUFFICIENT the first time, and that pam_unix succeeded. Your module is called again (// do something else here?) as sufficient. Two possibilities again:

    1. This time, your module succeeds: access granted.
    2. The module fails again, but for another reason than credentials insufficiency: access denied.

You might also want to execute custom code after UNIX authentication, even if it failed. In order to do that, change the second line to:

auth [success=ok default=bad]    pam_unix.so

This will make the chain go through my_module another time no matter what, but the chain will be marked as failed. Even if your module ends up successful here, the chain will fail.

You might also want to make your module aware of how many times we've called it in a chain: distinguish the first call to my_module from the second one. This could be done easily with arguments:

auth [cred_insufficient=ok success=done default=2] my_module.so
auth [success=ok default=1]      pam_unix.so
auth sufficient                  my_module.so second_time
auth requisite                   pam_deny.so

Here, the second call to pam_sm_authenticate will be passed an argument (through argv and argc) which should help you locate your module in the chain at runtime. Of course, your firstLogin condition should be enough to make such a distinction.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.