20041104 Thursday November 04, 2004

pam_netgroup

Not 10 minutes after I obliviated about Access Control in LDAP I was asked about 'pam_netgroup' -- I believe that there could be more than one iteration, but if you do a search on Google you get some hits about an UNSUPPORTED (as in WE DON't SUPPORT IT) piece of c code posted by a someone somewhere sometime ago that somehow could be somewhat related to pam_netgroup. However, it's a classic reference to writing your own custom PAM module -- yeah, that's the ticket -- you could sorta look at this code and compile it and test it; you know, just to make sure it works, before starting on your own. Hmmm, that's plausibly deniable enough. ( Nov 04 2004, 06:03:30 PM CST ) Permalink Comments [4]
Comments:

pam_access has proven to be very handy for me when managing multiple platforms. Taken from Linux-pam, and compiled for Solaris 2.6 - 8, HP-UX 11, it provides a very convenient way to manage host access in a cross-platform way.

Did I mention that I could configure host access on multiple platforms with the same configuration? That really simplifies things for those that are new to the environment or are not the type that can remember all of the trivia of multiple platforms.

From the sample access.conf...

...
#       permission : users : origins
#
# The first field should be a "+" (access granted) or "-" (access denied)
# character.
...
#
# If you run NIS you can use @netgroupname in host or user patterns; this
# even works for @usergroup@@hostgroup patterns. Weird.
#
...
Perhaps it is time for me to submit a feature request...

Posted by Mike Gerdts on November 05, 2004 at 07:25 AM CST #

The code for pam_access can be found in Linux-PAM at http://www.kernel.org/pub/linux/libs/pam/pre/library/Linux-PAM-0.78-Beta1.tar.gz. You can temporarily (however long that is...) get it at http://homepages.cae.wisc.edu/~gerdts/tmp/pam_access/

FWIW, it looks as though pam_netgroup and pam_access use essentially the same mechanisms, possibly with a bit more flexibility in pam_access.

One thing odd with pam_netgroup.c is the malloc() followed by a copy of the string. It looks like the malloc could be avoided altogether by changing:

			if ((netgroup = (char *)malloc(buflen)) == NULL) {
				perror("pam_netgroup: malloc");
				return (PAM_SERVICE_ERR);
			}
			for (i = 1, j = 0; i < buflen; ++i, ++j) {
				netgroup[j] = buf[i];
			}
			netgroup[j] = '\0';
to
			netgroup = &buf[1];
If not, there should probably be some calls to free() in a few places.

Posted by Mike Gerdts on November 05, 2004 at 10:05 PM CST #

Jah, I found the pam_netgroup.c module first -- Some guy at Sun released it under a "don't complain if it eats your mom" type license. But it was badly broken: beyond the unneeded (and never free()ed) malloc() mentioned above, there was also some seriously reversed security-checking logic which didn't work, and some pooly thought-out test conditions. The raw code itself was good enough though, and (with a quick macro to get rid of a spurious type-mismatch warning on Linux) it compiles cleanly and works well on both Linux and Solaris. Though I've ported Linux-PAM modules to Solaris before (pam_cracklib and pam_mkhomedir), pam_access had lots of crap that wasn't quite right for Solaris's PAM, and I didn't like the config file syntax. pam_netgroup is rediculously lightweight (133 lines sans comments) Below is my improved version of it, with the fixes mentioned above, if you're interested:
/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL SUN MICROSYSTEMS, INC. BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
 * OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF SUN
 * MICROSYSTEMS, INC. HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * SUN MICROSYSTEMS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THE SOFTWARE PROVIDED
 * HEREUNDER IS ON AN "AS IS" BASIS, AND SUN MICROSYSTEMS, INC. HAS NO
 * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
 * MODIFICATIONS.
 *
 */

#pragma ident   "%Z%%M% %I%     %E% SMI"

/*
 * pam_netgroup.c - restrict acces based on username or netgroup
 *                  Used to overcome the perfomance problems of using
 *                  passwd_compat in nsswitch.conf
 *
 * Compile:
 *
 *      cc pam_netgroup.c -o pam_netgroup.so.1 -Kpic -G
 *
 * Install:
 *
 *      cp pam_netgroup.so.1 /usr/lib/security
 *      chmod 644 /usr/lib/security/pam_netgroup.so.1
 *      ln -s pam_netgroup.so.1 /usr/lib/security/pam_netgroup.so
 * update the Account Managment section on /etc/pam.conf to be:
 * #
 * # Account management
 * #
 * login   account required        /usr/lib/security/$ISA/pam_netgroup.so.1
 * login   account required        /usr/lib/security/$ISA/pam_unix.so.1
 * dtlogin account required        /usr/lib/security/$ISA/pam_netgroup.so.1
 * dtlogin account required        /usr/lib/security/$ISA/pam_unix.so.1
 * #
 * other   account required        /usr/lib/security/$ISA/pam_netgroup.so.1
 * other   account required        /usr/lib/security/$ISA/pam_unix.so.1
 *
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <netdb.h>
#include <malloc.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>

#define ALLOW_FILE      "/etc/netgroups.allow"

#ifdef linux
# define Const const
#else
# define Const
#endif

#define debug fprintf

int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
        FILE    *allowfl;
        char    buf[BUFSIZ];
        char    *username = NULL;
        int     buflen = 0;
        char    *netgroup;
        char    *rhost;
        struct stat allowfl_stat;
        int     check_user = 1;
        int     check_host = 0;
        int     check_exact = 0;
        int     userok = 0;
        int     hostok = 0;
        int     i, j;

        for (i = 0; i < argc; ++i) {
                if (strcasecmp(argv[i], "user") == 0) {
                        check_user = 1;
                } else if (strcasecmp(argv[i], "nouser") == 0) {
                        check_user = 0;
                } else if (strcasecmp(argv[i], "host") == 0) {
                        check_host = 1;
                } else if (strcasecmp(argv[i], "exact") == 0) {
                        check_exact = 1;
                } else {
                        syslog(LOG_ERR, "PAM_NETGROUP pam_sm_acct_mgmt: "
                            "illegal option %s", argv[i]);
                        return (PAM_SERVICE_ERR);
                }
        }

        debug(stderr, "check_user = %d, check_host = %d, check_exact = %d\n",
            check_user, check_host, check_exact);
        if (lstat(ALLOW_FILE, &allowfl_stat) < 0) {
                syslog(LOG_ERR, "PAM_NETGROUP pam_sm_acct_mgmt: "
                    "lstat of %s failed", ALLOW_FILE);
                return (PAM_SERVICE_ERR);
        }

        if ((! S_ISREG(allowfl_stat.st_mode)) ||        /* no symbolic links */
            (allowfl_stat.st_nlink > 1) ||              /* no hard links */
            (allowfl_stat.st_uid != 0)) {               /* owned by root */
                syslog(LOG_ERR, "PAM_NETGROUP pam_sm_acct_mgmt: "
                    "%s isn't a regular file, has links or isn't owned by root\n", ALLOW_FILE);
                return (PAM_SERVICE_ERR);
        }

        if ((allowfl = fopen(ALLOW_FILE, "r")) == NULL) {
                return (PAM_SERVICE_ERR);
        }

        if (pam_get_item(pamh, PAM_USER, (Const void**)&username) != PAM_SUCCESS) {
                return (PAM_SERVICE_ERR);
        }
        pam_get_item(pamh, PAM_RHOST, (Const void**)&rhost);

        if (rhost != NULL)
                debug(stderr, "pam_netgroup:pam_sm_acct_mgt for (%s,%s,)\n",
                    rhost, username);
        else
                debug(stderr, "pam_netgroup:pam_sm_acct_mgt for (,%s,)\n",
                    username);

        while (fgets(buf, BUFSIZ, allowfl) != NULL) {
                buflen = strlen(buf);
                if ((buflen) && (buf[buflen - 1] == '\n')) { /* strip newlines */
                        buf[buflen - 1] = '\0';
                        buflen -= 1;
                }

                if (! buflen) continue; /* ignore empty lines */
                if (buf[0] == '#') continue; /* ignore comment lines */

                if ((buf[0] == '@') && (buf[1] != '\0')) {
                        netgroup = &buf[1];
                        debug(stderr, "Checking netgroup: %s\n", netgroup);

                        if (check_exact) {
                                if (innetgr(netgroup,
                                        rhost, username, NULL) == 1) {
                                        debug(stderr, "Found in netgroup %s\n",netgroup);
                                        return (PAM_SUCCESS);
                                }
                        } else {
                                userok = hostok = 0;
                                if (check_user) {
                                        userok = (innetgr(netgroup, NULL, username, NULL) == 1);
                                } else {
                                        userok = 1;
                                }
                                if (check_host) {
                                        hostok = innetgr(netgroup, rhost, NULL,NULL);
                                } else {
                                        hostok = 1;
                                }
                                if (userok && hostok) {
                                        debug(stderr, "Found user & host\n");
                                        return (PAM_SUCCESS);
                                }
                        }
                } else {
                        debug(stderr, "Checking user: %s\n", buf);
                        if (strcmp(buf, username) == 0) {
                                debug(stderr, "Found user %s\n", buf);
                                return (PAM_SUCCESS);
                        }
                }
        }

        return (PAM_AUTH_ERR);
}

Posted by Tom Williams on December 06, 2004 at 08:33 PM CST #

Grrr. Damed html parser on the website ate all the #include lines, even though I replaced them with html escape sequences (and surrounded everything with <pre> tags). If you want the correct include lines, email me and I'll send you the C source file.. t.

Posted by Tom Williams on December 06, 2004 at 08:41 PM CST #

Post a Comment:

Comments are closed for this entry.