/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * First known version of this code published internally at Sun in 1999.
 * It later appeared on SunSolve as part of an InfoDoc
 */

/*
 * 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/local/lib
 *	chmod 755 /usr/local/lib/pam_netgroup.so.1
 *
 * update /etc/pam.conf with something like this
 * 
 * other   account requisite   pam_roles.so.1
 * other   account required    pam_unix_account.so.1
 * other   account required    pam_netgroup.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/users.allow"

#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 is/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, (void**)&username) != PAM_SUCCESS) {
		return (PAM_SERVICE_ERR);
	}
	pam_get_item(pamh, PAM_RHOST, (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-1, allowfl) != NULL) {
		buflen = strlen(buf);
		if (buflen > 1) {
			buf[buflen - 1] = '\0';
			buflen -= 1;
		} else {
			return (PAM_SERVICE_ERR);
		}

		if (buf[0] == '#')
			continue;

		if ((buf[0] == '@') && (buf[1] != '\0')) {
			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';
			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);
}
