/*
 * pam_alreadyloggedin.c
 *
 * Rewritten for Linux at Jan 2004 by Ilya Evseev <ilya_evseev@mail.ru>
 * Version 0.3 with tiny patch from lbenini@csr.unibo.it at Dec 2004.
 *
 * Here is banner of original pam_alreadyloggedin module
 * taken from FreeBSD project source tree:
 *________________________________________________________
 *
 * Copyright (c) 2002 Brian Fundakowski Feldman
 * Copyright (c) 2002 Networks Associates Technologies, Inc.
 * All rights reserved.
 *
 * This software was developed by Robert Watson and Ilmar Habibulin for the
 * TrustedBSD Project.
 *
 * This software was developed for the FreeBSD Project in part by NAI Labs,
 * the Security Research Division of Network Associates, Inc. under
 * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA
 * CHATS research program.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The names of the authors may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $ Id: pam_alreadyloggedin.c,v 1.2 2002/03/07 16:54:40 green Exp $
 */

/*
 * Implement a PAM module which will, given restrictions upon whether the
 * user to be authenticated is root or logging in on a given terminal,
 * will allow the user to be authenticated successfully if the user
 * is currently already logged in on another terminal.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <err.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <utmp.h>

#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_misc.h>

/*--------  Reporting  ---------*/

#ifdef DEBUG
static int debug_enabled = 1;
#else
static int debug_enabled = 0;
#endif

#define DEBUG_LOG(fmt, args...) \
	do { if (debug_enabled) \
	syslog(LOG_DEBUG, "%s:%i " fmt , __FUNCTION__ , __LINE__ , ## args); \
	} while(0)

#define ERROR_LOG(fmt, args...) \
	syslog(LOG_ERR, "%s:%i " fmt , __FUNCTION__ , __LINE__ , ## args)

#define RETURN_ERROR_IF(_Condition, fmt, args...) \
	do { if (_Condition)  { ERROR_LOG(fmt, args); return -1; } } while(0)

#define RETURN_IF_ERROR(_Action, fmt, args...) \
	do { if (_Action < 0) { ERROR_LOG(fmt, args); return -1; } } while(0)

/* RETURN_IF_PAM_ERROR uses two predefined variables: 'pamh' and 'retval' */
#define RETURN_IF_PAM_ERROR(_Hint, _Action) \
	do { if ((retval = _Action) != PAM_SUCCESS) { \
    		ERROR_LOG("%s:%s", _Hint, pam_strerror(pamh,retval)); \
		return retval; \
	} } while(0)

/*---- end of reporting section ----*/

#ifdef BUG_STAT_MISSING

int stat(const char *filename, struct stat *statbuf)
{
	return __xstat(_STAT_VER, filename, statbuf);
}

#endif

static int getutmp(int *fd, struct utmp *utmp);
static int inutmp(struct utmp *utmp, const char *lineglob,
		const char *username, uid_t uid);

static int test_option(int argc, const char **argv,
	const char *option_name, const char **option_value_ref)
{
	int namelen = strlen(option_name);
	int argnum;
	for(argnum = 0; argnum < argc; argnum++) {
		const char *arg = argv[argnum];
		int arglen = strlen(arg);
		if (arglen < namelen)
			continue;
		if (memcmp(option_name, arg, namelen))
			continue;
		if (option_value_ref) {
			if (arglen == namelen) {
				*option_value_ref = NULL;
			} else {
				const char *p = arg+namelen;
				if ((*p != ':') && (*p != '='))
					continue;
				*option_value_ref = p+1;
			}
		}
		DEBUG_LOG("option=\"%s\", value=\"%s\"", option_name,
			( (option_value_ref && *option_value_ref) ?
			    *option_value_ref : "(null)" )
		);
		return 1;  /* ..found */
	}  /* for(argv) */
	return 0;  /* ..not found */
}

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
	struct utmp utmp;
	struct passwd *pw;
	const char *logname;
	const char *lineglob, *loggedinlineglob = NULL;
	unsigned int matched = 0;
	int retval, fd = -1;

	if (test_option(argc, argv, "no_debug", NULL)) {
		DEBUG_LOG("Debugging output disabled");
		debug_enabled = 0;
	}
	if (test_option(argc, argv, "debug", NULL)) {
		debug_enabled = 1;
		DEBUG_LOG("Debugging output enabled");
	}
		
        RETURN_IF_PAM_ERROR("pam_get_user", pam_get_user(pamh, &logname, NULL));
	DEBUG_LOG("logname = \"%s\"", logname);

	if (test_option(argc, argv, "restrict_tty", &lineglob) && lineglob) {
		const char *pam_tty;

		RETURN_IF_PAM_ERROR("pam_get_item(PAM_TTY)",
			pam_get_item(pamh, PAM_TTY, (const void **)&pam_tty));
		DEBUG_LOG("pam_tty = \"%s\"", pam_tty);

		if (fnmatch(lineglob, pam_tty, 0) != 0) {
			DEBUG_LOG("mask(\"%s\") != pam_tty(\"%s\")",
				lineglob, pam_tty);
			return PAM_AUTH_ERR;
		}
	}
	test_option(argc, argv, "restrict_loggedin_tty", &loggedinlineglob);
	if ((pw = getpwnam(logname)) == NULL)
		return PAM_AUTH_ERR;
	if (pw->pw_uid == 0 && test_option(argc, argv, "no_root", NULL))
		return PAM_AUTH_ERR;
	while (getutmp(&fd, &utmp) == 1) {
		if (inutmp(&utmp, loggedinlineglob, logname, pw->pw_uid) == 1)
			matched++;
	}
	DEBUG_LOG("Found matching records in utmp: %d", matched);
	if (matched)
		return PAM_SUCCESS;
	return PAM_AUTH_ERR;
}

/*
 *  This function is really empty,
 *  but without it Linux PAM displays following warning:
 *  "login: PAM unable to resolve symbol: pam_sm_setcred"
 */
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
	return PAM_SUCCESS;
}


#ifdef PAM_STATIC

struct pam_module _pam_alreadyloggenin_modstruct = {
        "pam_alreadyloggedin",
	pam_sm_authenticate,
	pam_sm_setcred
};

#endif

int getutmp(int *fd, struct utmp *utmp)
{
	if (*fd < 0)
		*fd = open(_PATH_UTMP, O_RDONLY);
	RETURN_IF_ERROR(*fd, "Failure opening %s", _PATH_UTMP);
	if (read(*fd, utmp, sizeof(*utmp)) == sizeof(*utmp))
		return 1;
	close(*fd);
	return 0;
}

int
inutmp(struct utmp *utmp, const char *lineglob, const char *username, uid_t uid)
{
	char ttypath[MAXPATHLEN];
	struct stat sb;

	if (utmp->ut_name[0] == '\0' || utmp->ut_line[0] == '\0')
		return 0;
	utmp->ut_line[sizeof(utmp->ut_line) - 1] = '\0';
	utmp->ut_name[sizeof(utmp->ut_name) - 1] = '\0';

	if (*username && strcmp(username, utmp->ut_name))
		return 0;

#if defined(ENABLE_EVIL_UTMP_CHECK)
	// This should be omitted on modern linuxes,
	// thanks to lbenini@csr.unibo.it
	RETURN_ERROR_IF(utmp->ut_line[strcspn(utmp->ut_line, "./")] != '\0',
		"Evil utmp line: `%s'", utmp->ut_line);
#endif

	snprintf(ttypath, sizeof(ttypath), "/dev/%s", utmp->ut_line);
	if (lineglob && (fnmatch(lineglob, ttypath, 0) != 0)) {
		DEBUG_LOG("mask(\"%s\") != utmp_tty(\"%s\")",
			lineglob, ttypath);
		return 0;
	}

	/* can't fail */
	RETURN_IF_ERROR(stat(ttypath, &sb), "Can't stat line \"%s\"", ttypath);
	RETURN_ERROR_IF(sb.st_uid != uid,
		"UID of ttyline %d does not match %d", sb.st_uid, uid);
	return 1;  //ok, true result
}

/* EOF */
