LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Can't get auth token for non-local users with PAM module (https://www.linuxquestions.org/questions/programming-9/cant-get-auth-token-for-non-local-users-with-pam-module-945164/)

commx 05-15-2012 05:08 PM

Can't get auth token for non-local users with PAM module
 
I've wrote a PAM module that should provide authentication for linux hosts with user accounts that are managed from a central site. Reading the username works fine, but the password is some garbage when the specified username is not existing on the local system, for example:

Code:

May 15 23:59:10 localhost sshd[24920]: pam_test[24920] NOTICE: got username 'test'
May 15 23:59:10 localhost sshd[24920]: pam_test[24920] NOTICE: authtok '^H
^M^?INCORRECT'

For me, it is required to be able to collect both username and password first, even if the specified username is not a local system account. Both informations are sent to a central authentication server in the background, which will reply the appropriate user account then (if the authentication succeeds).

The code for my PAM module:

Code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>
#include <security/_pam_macros.h>


PAM_EXTERN
int pam_sm_authenticate(pam_handle_t *pamh, int flags,
                        int argc, const char **argv)
{
        int retval, ctrl;
        const char *user, *password;
        char *resp = NULL, *pass;
       
        retval = pam_get_user(pamh, &user, NULL);
        if (retval != PAM_SUCCESS || user == NULL) {
                syslog(LOG_ALERT, "pam_test[%d] ERROR: no user specified", getpid());
                return PAM_USER_UNKNOWN;
        } else {
                syslog(LOG_WARNING, "pam_test[%d] NOTICE: got username '%s'", getpid(),
                                user);
        }

        retval = pam_get_authtok(pamh, PAM_AUTHTOK, (const char **)&pass, NULL);
        syslog(LOG_WARNING, "pam_test[%d] NOTICE: authtok '%s'", getpid(), pass);

        return PAM_USER_UNKNOWN;
}

Any help is appreciated.

Nominal Animal 05-15-2012 06:23 PM

pam_get_authtok() is OpenPAM shorthand.

If the password is supposed to have been asked already by a prior module in the chain, you can obtain it via retval = pam_get_item(pamh, PAM_AUTHTOK, password);. Normally, though, you need to start a PAM conversation, and ask the user for a password instead.

For details, see Linux-PAM:modules/pam_unix/support.c:_unix_read_password() (at the end of the file).

commx 05-15-2012 07:14 PM

I've tried pam_get_authtok(), pam_prompt() and pam_get_item() since then, they all return "INCORRECT garbage" in the response variable. This only happens when I'm trying to use a username that does not exist on the local system, otherwise these functions return the correct password.

Nominal Animal 05-15-2012 09:22 PM

Quote:

Originally Posted by commx (Post 4679412)
they all return "INCORRECT garbage" in the response variable. This only happens when I'm trying to use a username that does not exist on the local system, otherwise these functions return the correct password.

Sorry; I was a bit scattered earlier.

I have actually discussed exactly the same issue in an earlier thread, where chesschi wanted to use FreeRadius for authentication.

The problem is that Linux PAM will replace the password with "\b\n\r\177INCORRECT" if it cannot obtain information regarding the user from the system databases (Name Service Switch, see man nsswitch.conf).

That cannot be simply edited out, either. You must have some way of providing the user information -- basically everything except the password -- to the system databases. You can write a dynamic nss library, use NIS+, or use LDAP to provide the remote user information, but it has to be provided somehow. A PAM module alone is not enough.

Can you set up an LDAP server (say, as a frontend for your user database) for providing the public information on your users? If not, I am afraid you'll have to write user information daemon (one for client-side machines, and maybe one for your user database side), and a dynamic nss library that connects to the local user information daemon. Trust me, LDAP is much simpler.

commx 05-17-2012 07:56 AM

That seems to be reasonable. Thank you for your hint.

commx 05-20-2012 04:56 PM

So now I've gone a step further and writed a simple NSS module. According to this thread, it shouldn't be a problem to do so. While my NSS module seems to work when using su(do), id etc., its debug functions don't get called when trying to login via ssh. Seems like sshd does not use NSS to lookup system users?

What I've done so far:
  • writing a authentication library for my authentication provider (basically JSON-based HTTP requests).
  • writing a PAM module that asks for the username and password; these both things are checked via the authentication library whether the username/password combination is valid. If so, a username is returned that represents a username on the local system. This username is also present in /etc/passwd.
  • writing a NSS module that defines all required functions. The _nss_test_getpwbynam_r() function uses the authentication library to fetch the local user name for the username that has been provided when trying to login via ssh and writes it into the result passwd struct's pw_name attribute (and returns NSS_STATUS_SUCCESS).

I've also tried to set a static local user account for whatever username is entered when trying to log in (via pam_set_item(pamh, PAM_USER, (const void **)"some_static_user") - but that didn't work too.

To explain what I want to achieve with that:
I want to grant a set of users access to specific servers. The UNIX user account they've access on the target server is stored in a central database and they should be logged in using that user name when they try to login with their personal username/password combination.

Code for the actual NSS module:
Code:

#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <nss.h>
#include <syslog.h>
#include <test_auth.h>

/** Holds a temporary user name. */
static char *temp_username = NULL;

/**
 * Prepare buffers.
 */
enum nss_status _nss_test_setpwent(void)
{
        syslog(LOG_ERR, "_nss_test_setpwent() called");
        return NSS_STATUS_SUCCESS;
}

/**
 * Free buffers.
 */
enum nss_status _nss_test_endpwent(void)
{
        syslog(LOG_ERR, "_nss_test_endpwent() called");

        if (temp_username) {
                free(temp_username);
                temp_username = NULL;
        }

        return NSS_STATUS_SUCCESS;
}

enum nss_status _nss_test_getpwent_r(struct passwd *result, char *buffer, size_t buflen, int *errnop)
{
        syslog(LOG_ERR, "_nss_test_getpwent_r() called");
        return NSS_STATUS_SUCCESS;
}

enum nss_status _nss_test_getpwbyuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errnop)
{
        syslog(LOG_ERR, "_nss_test_getpwbyuid_r() called");
        return NSS_STATUS_SUCCESS;
}

enum nss_status _nss_test_getpwbynam_r(const char *name, struct passwd *result,
                                      char *buffer, size_t buflen, int *errnop)
{
        struct TEST_auth_response response;
        json_t *local_user = NULL;

        syslog(LOG_ERR, "_nss_test_getpwbynam_r() called");
        if (test_auth_query_local_user(name, "localhost", &response) == 0) {
                if ((local_user = json_object_get(response.response, "local_user"))) {
                        temp_username = strdup(json_string_value(local_user));
                        result->pw_name = temp_username;
                        return NSS_STATUS_SUCCESS;
                }
        }
       
        test_auth_free_response(&response);

        return NSS_STATUS_SUCCESS;
}


Nominal Animal 05-20-2012 05:06 PM

Quote:

Originally Posted by commx (Post 4683463)
Seems like sshd does not use NSS to lookup system users?

After installing the NSS library files and updating the /etc/nsswitch.conf configuration file, did you remember to update the dynamic linker cache by running ldconfig -v as root? You'll probably also have to restart long-running services like ssh to make sure they see the new NSS libraries, too.

Edited to add: If you use nscd, you'll have to clear its cache too by running nscd -i passwd as root.

commx 05-22-2012 05:20 AM

After hours of trying and such, I'll figured it out. The function names were wrong. For some reason I've wrote the functions to be like _nss_NAME_getpwby*_r() instead of _nss_NAME_getpw*_r(). That's why _nss_NAME_endpwent() and _nss_NAME_setpwent() were called but the actual Nevertheless I've got it working and I am now able to read the password correctly even for users who don't exist locally.

Again, thank you for your efforts!

Nominal Animal 05-22-2012 02:15 PM

Quote:

Originally Posted by commx (Post 4684631)
After hours of trying and such, I'll figured it out. The function names were wrong. For some reason I've wrote the functions to be like _nss_NAME_getpwby*_r() instead of _nss_NAME_getpw*_r().

Darnit, I missed that too, even though I did read your code quite carefully! :doh: :)

Anyway, it's very nice to hear that it works now.

I take it that this solves your original problem? Would you mind recapping your findings in a final post, and marking the thread solved? I'm certain it would be very useful for others reading this thread in the future.


All times are GMT -5. The time now is 12:10 AM.