LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 05-15-2012, 06:08 PM   #1
commx
LQ Newbie
 
Registered: Sep 2003
Distribution: Arch
Posts: 16

Rep: Reputation: 0
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.
 
Old 05-15-2012, 07:23 PM   #2
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947
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).
 
Old 05-15-2012, 08:14 PM   #3
commx
LQ Newbie
 
Registered: Sep 2003
Distribution: Arch
Posts: 16

Original Poster
Rep: Reputation: 0
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.
 
Old 05-15-2012, 10:22 PM   #4
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947
Quote:
Originally Posted by commx View Post
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.
 
Old 05-17-2012, 08:56 AM   #5
commx
LQ Newbie
 
Registered: Sep 2003
Distribution: Arch
Posts: 16

Original Poster
Rep: Reputation: 0
That seems to be reasonable. Thank you for your hint.
 
Old 05-20-2012, 05:56 PM   #6
commx
LQ Newbie
 
Registered: Sep 2003
Distribution: Arch
Posts: 16

Original Poster
Rep: Reputation: 0
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;
}
 
Old 05-20-2012, 06:06 PM   #7
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947
Quote:
Originally Posted by commx View Post
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.

Last edited by Nominal Animal; 05-20-2012 at 06:16 PM.
 
Old 05-22-2012, 06:20 AM   #8
commx
LQ Newbie
 
Registered: Sep 2003
Distribution: Arch
Posts: 16

Original Poster
Rep: Reputation: 0
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!
 
Old 05-22-2012, 03:15 PM   #9
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947Reputation: 947
Quote:
Originally Posted by commx View Post
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!

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.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
PAM/NSS: Local & Global users synchronization problem Mareq Linux - Security 0 10-22-2011 08:05 AM
PAM (system-auth) illegal module type: ccount meellaz Linux - Software 3 03-09-2011 07:23 AM
PAM (system-auth) illegal module type: ccount meellaz Linux - Security 1 03-09-2011 01:08 AM
Apache LDAP auth with local users - getting warnings - possible to suppress? laggerific Linux - Software 1 08-18-2009 07:32 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 09:15 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration