PAM modules: pam_mkhomedir for Solaris

There is a PAM module available for creating home directories on fly, and this module is pam_mkhomedir.so. This is quite useful if you have a LDAP server ( in this case Directory Server 6.3) and you are inserting users but their home directories were not created. To my surprise, this module it was not available for Solaris, but I found an interesting link with useful information about this.
First of all , you’ll need to download the files Linux-PAM-80. For compiling this I did a little script, after reading the above article:

#!/bin/bash

PATH=/usr/sfw/bin:/usr/ccs/bin:$PATH;export PATH

gcc -c -g -O2 -D_REENTRANT -DPAM_DYNAMIC -Wall

-fPIC -I../../libpam/include \

-I../../libpamc/include   \

-I../pammodutil/include pam_mkhomedir.c

After compiling the module was not working, so  What should I do now? Well, I tried to debug why the module was not working, somehow I tried differents methods, the first I actived debug mode in syslog daemon, you only need to add

*.debug /var/adm/pam_log

in the /etc/syslog.conf . But here is what I found:

ay 18 10:27:25 des-to16-d sshd[26177]: [ID 547715 auth.debug] PAM[26177]: load_function: successful load of pam_sm_setcred
May 18 10:27:25 des-to16-d sshd[26177]: [ID 482737 auth.debug] PAM[26177]: pam_open_session(8a828, 0)
May 18 10:27:25 des-to16-d sshd[26177]: [ID 926797 auth.debug] PAM[26177]: load_modules(8a828, pam_sm_open_session)=/usr/lib/security/pam_mkhomedir.so

Nothing special that points me out how to solve this, so I tried another way. I tried to log with a ldap’s user, first with ssh but I was always getting out from the system, so that something interesting and annoying was happening. I thought to use telnet and I got this:

login: user1
Password:
ld.so.1: login: fatal: relocation error: file /usr/lib/security/pam_mkhomedir.so: symbol _pammodutil_getpwnam: referenced symbol not found
Connection to localhost closed by foreign host.

That told me more!! I opened pam_mkhomedir.c and I found the name of four functions :

_pammodutil_getpwnam
_pammodutil_read
_pammodutil_write
_pammodutil_cleanup

that there are no available in Solaris 10 ( of course neither above versions ). What did I do? I put all these functions together in the same file, and I added some includes, so this is all the code you need to add pam_mkhomedir.c before compiling:

/***** These includes are needed ********/

#include <security/pammodutil.h>
#include <errno.h>
#include <limits.h>

/*********** Functions ***************/
void _pammodutil_cleanup(pam_handle_t *pamh, void *data, int error_status)
{
    if (data) {
	/* junk it */
	(void) free(data);
    }
}
int _pammodutil_read(int fd, char *buffer, int count)
{
       int block, offset = 0;

       while (count > 0) {
               block = read(fd, &amp;amp;amp;amp;buffer[offset], count);

               if (block < 0) {
                       if (errno == EINTR) continue;
                       return block;
               }
               if (block == 0) return offset;

               offset += block;
               count -= block;
       }

       return offset;
}
int _pammodutil_write(int fd, const char *buffer, int count)
{
       int block, offset = 0;

       while (count > 0) {
               block = write(fd, &amp;amp;amp;amp;buffer[offset], count);

               if (block < 0) {
                       if (errno == EINTR) continue;
                       return block;
               }
               if (block == 0) return offset;

               offset += block;
               count -= block;
       }

       return offset;
}

struct passwd *_pammodutil_getpwnam(pam_handle_t *pamh, const char *user)
{
#ifdef HAVE_GETPWNAM_R

    void *buffer=NULL;
    size_t length = PWD_INITIAL_LENGTH;

    do {
	int status;
	void *new_buffer;
	struct passwd *result = NULL;

	new_buffer = realloc(buffer, sizeof(struct passwd) + length);
	if (new_buffer == NULL) {

	    D(("out of memory"));

	    /* no memory for the user - so delete the memory */
	    if (buffer) {
		free(buffer);
	    }
	    return NULL;
	}
	buffer = new_buffer;

	/* make the re-entrant call to get the pwd structure */
	errno = 0;
	status = getpwnam_r(user, buffer,
			    sizeof(struct passwd) + (char *) buffer,
			    length, &amp;amp;amp;amp;result);
	if (!status &amp;amp;amp;amp;&amp;amp;amp;amp; (result == buffer)) {
	    char *data_name;
	    const void *ignore;
	    int i;

	    data_name = malloc(strlen("_pammodutil_getpwnam") + 1 +
	    		       strlen(user) + 1 + intlen(INT_MAX) + 1);
	    if ((pamh != NULL) &amp;amp;amp;amp;&amp;amp;amp;amp; (data_name == NULL)) {
	        D(("was unable to register the data item [%s]",
	           pam_strerror(pamh, status)));
		free(buffer);
		return NULL;
	    }

	    if (pamh != NULL) {
	        for (i = 0; i < INT_MAX; i++) {
	            sprintf(data_name, "_pammodutil_getpwnam_%s_%d", user, i);
	            _pammodutil_lock();
		    status = PAM_NO_MODULE_DATA;
	            if (pam_get_data(pamh, data_name, &amp;amp;amp;amp;ignore) != PAM_SUCCESS) {
		        status = pam_set_data(pamh, data_name,
					      result, _pammodutil_cleanup);
		    }
	            _pammodutil_unlock();
		    if (status == PAM_SUCCESS) {
		        break;
		    }
		}
	    } else {
	        status = PAM_SUCCESS;
	    }

	    free(data_name);

	    if (status == PAM_SUCCESS) {
		D(("success"));
		return result;
	    }

	    D(("was unable to register the data item [%s]",
	       pam_strerror(pamh, status)));

	    free(buffer);
	    return NULL;

	} else if (errno != ERANGE &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; errno != EINTR) {
                /* no sense in repeating the call */
                break;
        }

	length <<= 2;

    } while (length < PWD_ABSURD_PWD_LENGTH);

    D(("pwd structure took %u bytes or so of memory",
       length+sizeof(struct passwd)));

    free(buffer);
    return NULL;

#else /* ie. ifndef HAVE_GETPWNAM_R */

    /*
     * Sorry, there does not appear to be a reentrant version of
     * getpwnam(). So, we use the standard libc function.
     */

    return getpwnam(user);

#endif /* def HAVE_GETPWNAM_R */
}

/**************** end ****************/

Obiously I hope nobody tries to C&P, I think it would better to look for the functions in the include files and then copy and paste, just because I could made some mistakes coping these lines.After compiling you can copy pam_mkhomedir.so to /usr/lib/security and do not forget to add the following line to the “/etc/pam.conf”

other session required pam_mkhomedir.so skel=/etc/skel umask=0022

Now you can try to log in the system with a ldap’s user:

ssh -l user5 localhost
Password:
Creating directory ‘/export/home/user5′.

Last login: Thu May 14 17:16:21 2009 from localhost
-bash-3.00$

You also can try to access by telnet. The is also backward compability among different versions of Solaris, so that means, it will work out in Solaris 8,9 as well. I hope this information can be useful for somebody.