Blob Blame History Raw
/*
 * Copyright (c) 2010, 2017, 2019 Tomas Mraz <tmraz@redhat.com>
 * Copyright (c) 2010, 2017, 2019 Red Hat, Inc.
 *
 * 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, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 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 name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * ALTERNATIVELY, this product may be distributed under the terms of
 * the GNU Public License, in which case the provisions of the GPL are
 * required INSTEAD OF the above restrictions.  (This clause is
 * necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 *
 * THIS SOFTWARE IS PROVIDED ``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 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.
 */

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <pwd.h>
#include <syslog.h>
#include <ctype.h>

#ifdef HAVE_LIBAUDIT
#include <libaudit.h>
#endif

#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>

#include "faillock.h"

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT

#define FAILLOCK_ACTION_PREAUTH  0
#define FAILLOCK_ACTION_AUTHSUCC 1
#define FAILLOCK_ACTION_AUTHFAIL 2

#define FAILLOCK_FLAG_DENY_ROOT		0x1
#define FAILLOCK_FLAG_AUDIT		0x2
#define FAILLOCK_FLAG_SILENT		0x4
#define FAILLOCK_FLAG_NO_LOG_INFO	0x8
#define FAILLOCK_FLAG_UNLOCKED		0x10
#define FAILLOCK_FLAG_LOCAL_ONLY	0x20

#define MAX_TIME_INTERVAL 604800 /* 7 days */
#define FAILLOCK_CONF_MAX_LINELEN 1023
#define FAILLOCK_ERROR_CONF_OPEN -3
#define FAILLOCK_ERROR_CONF_MALFORMED -4

#define PATH_PASSWD "/etc/passwd"

struct options {
	unsigned int action;
	unsigned int flags;
	unsigned short deny;
	unsigned int fail_interval;
	unsigned int unlock_time;
	unsigned int root_unlock_time;
	char *dir;
	const char *conf;
	const char *user;
	char *admin_group;
	int failures;
	uint64_t latest_time;
	uid_t uid;
	int is_admin;
	uint64_t now;
	int fatal_error;
};

int read_config_file(
	pam_handle_t *pamh,
	struct options *opts,
	const char *cfgfile
);

void set_conf_opt(
	pam_handle_t *pamh,
	struct options *opts,
	const char *name,
	const char *value
);

static void
args_parse(pam_handle_t *pamh, int argc, const char **argv,
		int flags, struct options *opts)
{
	int i;
	int rv;
	memset(opts, 0, sizeof(*opts));

	opts->dir = strdup(FAILLOCK_DEFAULT_TALLYDIR);
	opts->conf = FAILLOCK_DEFAULT_CONF;
	opts->deny = 3;
	opts->fail_interval = 900;
	opts->unlock_time = 600;
	opts->root_unlock_time = MAX_TIME_INTERVAL+1;

	if ((rv=read_config_file(pamh, opts, opts->conf)) != PAM_SUCCESS) {
		pam_syslog(pamh, LOG_DEBUG,
					"Configuration file missing");
	}

	for (i = 0; i < argc; ++i) {
		if (strcmp(argv[i], "preauth") == 0) {
			opts->action = FAILLOCK_ACTION_PREAUTH;
		}
		else if (strcmp(argv[i], "authfail") == 0) {
			opts->action = FAILLOCK_ACTION_AUTHFAIL;
		}
		else if (strcmp(argv[i], "authsucc") == 0) {
			opts->action = FAILLOCK_ACTION_AUTHSUCC;
		}
		else {
			char buf[FAILLOCK_CONF_MAX_LINELEN + 1];
			char *val;

			strncpy(buf, argv[i], sizeof(buf) - 1);
			buf[sizeof(buf) - 1] = '\0';

			val = strchr(buf, '=');
			if (val != NULL) {
				*val = '\0';
				++val;
			}
			else {
				val = buf + sizeof(buf) - 1;
			}
			set_conf_opt(pamh, opts, buf, val);
		}
	}

	if (opts->root_unlock_time == MAX_TIME_INTERVAL+1)
		opts->root_unlock_time = opts->unlock_time;
	if (flags & PAM_SILENT)
		opts->flags |= FAILLOCK_FLAG_SILENT;

	if (opts->dir == NULL) {
		pam_syslog(pamh, LOG_CRIT, "Error allocating memory: %m");
		opts->fatal_error = 1;
	}
}

/* parse a single configuration file */
int
read_config_file(pam_handle_t *pamh, struct options *opts, const char *cfgfile)
{
	FILE *f;
	char linebuf[FAILLOCK_CONF_MAX_LINELEN+1];

	f = fopen(cfgfile, "r");
	if (f == NULL) {
		/* ignore non-existent default config file */
		if (errno == ENOENT && strcmp(cfgfile, FAILLOCK_DEFAULT_CONF) == 0)
			return 0;
		return FAILLOCK_ERROR_CONF_OPEN;
	}

	while (fgets(linebuf, sizeof(linebuf), f) != NULL) {
		size_t len;
		char *ptr;
		char *name;
		int eq;

		len = strlen(linebuf);
		/* len cannot be 0 unless there is a bug in fgets */
		if (len && linebuf[len - 1] != '\n' && !feof(f)) {
			(void) fclose(f);
			return FAILLOCK_ERROR_CONF_MALFORMED;
		}

		if ((ptr=strchr(linebuf, '#')) != NULL) {
			*ptr = '\0';
		} else {
			ptr = linebuf + len;
		}

		/* drop terminating whitespace including the \n */
		while (ptr > linebuf) {
			if (!isspace(*(ptr-1))) {
				*ptr = '\0';
				break;
			}
			--ptr;
		}

		/* skip initial whitespace */
		for (ptr = linebuf; isspace(*ptr); ptr++);
		if (*ptr == '\0')
			continue;

		/* grab the key name */
		eq = 0;
		name = ptr;
		while (*ptr != '\0') {
			if (isspace(*ptr) || *ptr == '=') {
				eq = *ptr == '=';
				*ptr = '\0';
				++ptr;
				break;
			}
			++ptr;
		}

		/* grab the key value */
		while (*ptr != '\0') {
			if (*ptr != '=' || eq) {
				if (!isspace(*ptr)) {
					break;
				}
			} else {
				eq = 1;
			}
			++ptr;
		}

		/* set the key:value pair on opts */
		set_conf_opt(pamh, opts, name, ptr);
	}

	(void)fclose(f);
	return PAM_SUCCESS;
}

void set_conf_opt(pam_handle_t *pamh, struct options *opts, const char *name, const char *value)
{
	if (strcmp(name, "dir") == 0) {
		if (value[0] != '/') {
			pam_syslog(pamh, LOG_ERR,
				"Tally directory is not absolute path (%s); keeping default", value);
		} else {
			free(opts->dir);
			opts->dir = strdup(value);
		}
	}
	else if (strcmp(name, "deny") == 0) {
		if (sscanf(value, "%hu", &opts->deny) != 1) {
			pam_syslog(pamh, LOG_ERR,
				"Bad number supplied for deny argument");
		}
	}
	else if (strcmp(name, "fail_interval") == 0) {
		unsigned int temp;
		if (sscanf(value, "%u", &temp) != 1 ||
			temp > MAX_TIME_INTERVAL) {
			pam_syslog(pamh, LOG_ERR,
				"Bad number supplied for fail_interval argument");
		} else {
			opts->fail_interval = temp;
		}
	}
	else if (strcmp(name, "unlock_time") == 0) {
		unsigned int temp;

		if (strcmp(value, "never") == 0) {
			opts->unlock_time = 0;
		}
		else if (sscanf(value, "%u", &temp) != 1 ||
			temp > MAX_TIME_INTERVAL) {
			pam_syslog(pamh, LOG_ERR,
				"Bad number supplied for unlock_time argument");
		}
		else {
			opts->unlock_time = temp;
		}
	}
	else if (strcmp(name, "root_unlock_time") == 0) {
		unsigned int temp;

		if (strcmp(value, "never") == 0) {
			opts->root_unlock_time = 0;
		}
		else if (sscanf(value, "%u", &temp) != 1 ||
			temp > MAX_TIME_INTERVAL) {
			pam_syslog(pamh, LOG_ERR,
				"Bad number supplied for root_unlock_time argument");
		} else {
			opts->root_unlock_time = temp;
		}
	}
	else if (strcmp(name, "admin_group") == 0) {
		free(opts->admin_group);
		opts->admin_group = strdup(value);
		if (opts->admin_group == NULL) {
			opts->fatal_error = 1;
			pam_syslog(pamh, LOG_CRIT, "Error allocating memory: %m");
		}
	}
	else if (strcmp(name, "even_deny_root") == 0) {
		opts->flags |= FAILLOCK_FLAG_DENY_ROOT;
	}
	else if (strcmp(name, "audit") == 0) {
		opts->flags |= FAILLOCK_FLAG_AUDIT;
	}
	else if (strcmp(name, "silent") == 0) {
		opts->flags |= FAILLOCK_FLAG_SILENT;
	}
	else if (strcmp(name, "no_log_info") == 0) {
		opts->flags |= FAILLOCK_FLAG_NO_LOG_INFO;
	}
	else if (strcmp(name, "local_users_only") == 0) {
		opts->flags |= FAILLOCK_FLAG_LOCAL_ONLY;
	}
	else {
		pam_syslog(pamh, LOG_ERR, "Unknown option: %s", name);
	}
}

static int check_local_user (pam_handle_t *pamh, const char *user)
{
	struct passwd pw, *pwp;
	char buf[4096];
	int found = 0;
	FILE *fp;
	int errn;

	fp = fopen(PATH_PASSWD, "r");
	if (fp == NULL) {
		pam_syslog(pamh, LOG_ERR, "unable to open %s: %m",
			   PATH_PASSWD);
		return -1;
	}

	for (;;) {
		errn = fgetpwent_r(fp, &pw, buf, sizeof (buf), &pwp);
		if (errn == ERANGE) {
			pam_syslog(pamh, LOG_WARNING, "%s contains very long lines; corrupted?",
				   PATH_PASSWD);
			/* we can continue here as next call will read further */
			continue;
		}
		if (errn != 0)
			break;
		if (strcmp(pwp->pw_name, user) == 0) {
			found = 1;
			break;
		}
	}

	fclose (fp);

	if (errn != 0 && errn != ENOENT) {
		pam_syslog(pamh, LOG_ERR, "unable to enumerate local accounts: %m");
		return -1;
	} else {
		return found;
	}
}

static int get_pam_user(pam_handle_t *pamh, struct options *opts)
{
	const char *user;
	int rv;
	struct passwd *pwd;

	if ((rv=pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) {
		return rv;
	}

	if (*user == '\0') {
		return PAM_IGNORE;
	}

	if ((pwd=pam_modutil_getpwnam(pamh, user)) == NULL) {
		if (opts->flags & FAILLOCK_FLAG_AUDIT) {
			pam_syslog(pamh, LOG_ERR, "User unknown: %s", user);
		}
		else {
			pam_syslog(pamh, LOG_ERR, "User unknown");
		}
		return PAM_IGNORE;
	}
	opts->user = user;
	opts->uid = pwd->pw_uid;

	if (pwd->pw_uid == 0) {
		opts->is_admin = 1;
		return PAM_SUCCESS;
	}

	if (opts->admin_group && *opts->admin_group) {
		opts->is_admin = pam_modutil_user_in_group_uid_nam(pamh,
			pwd->pw_uid, opts->admin_group);
	}

	return PAM_SUCCESS;
}

static int
check_tally(pam_handle_t *pamh, struct options *opts, struct tally_data *tallies, int *fd)
{
	int tfd;
	unsigned int i;
	uint64_t latest_time;
	int failures;

	opts->now = time(NULL);

	tfd = open_tally(opts->dir, opts->user, opts->uid, 0);

	*fd = tfd;

	if (tfd == -1) {
		if (errno == EACCES || errno == ENOENT) {
			return PAM_SUCCESS;
		}
		pam_syslog(pamh, LOG_ERR, "Error opening the tally file for %s: %m", opts->user);
		return PAM_SYSTEM_ERR;
	}

	if (read_tally(tfd, tallies) != 0) {
		pam_syslog(pamh, LOG_ERR, "Error reading the tally file for %s: %m", opts->user);
		return PAM_SYSTEM_ERR;
	}

	if (opts->is_admin && !(opts->flags & FAILLOCK_FLAG_DENY_ROOT)) {
		return PAM_SUCCESS;
	}

	latest_time = 0;
	for(i = 0; i < tallies->count; i++) {
		if ((tallies->records[i].status & TALLY_STATUS_VALID) &&
			tallies->records[i].time > latest_time)
			latest_time = tallies->records[i].time;
	}

	opts->latest_time = latest_time;

	failures = 0;
	for(i = 0; i < tallies->count; i++) {
		if ((tallies->records[i].status & TALLY_STATUS_VALID) &&
			latest_time - tallies->records[i].time < opts->fail_interval) {
			++failures;
		}
	}

	opts->failures = failures;

	if (opts->deny && failures >= opts->deny) {
		if ((!opts->is_admin && opts->unlock_time && latest_time + opts->unlock_time < opts->now) ||
			(opts->is_admin && opts->root_unlock_time && latest_time + opts->root_unlock_time < opts->now)) {
#ifdef HAVE_LIBAUDIT
			if (opts->action != FAILLOCK_ACTION_PREAUTH) { /* do not audit in preauth */
				char buf[64];
				int audit_fd;
				const void *rhost = NULL, *tty = NULL;

				audit_fd = audit_open();
				/* If there is an error & audit support is in the kernel report error */
				if ((audit_fd < 0) && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
					errno == EAFNOSUPPORT))
					return PAM_SYSTEM_ERR;

				(void)pam_get_item(pamh, PAM_TTY, &tty);
				(void)pam_get_item(pamh, PAM_RHOST, &rhost);
				snprintf(buf, sizeof(buf), "pam_faillock uid=%u ", opts->uid);
				audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf,
					rhost, NULL, tty, 1);
			}
#endif
			opts->flags |= FAILLOCK_FLAG_UNLOCKED;
			return PAM_SUCCESS;
		}
		return PAM_AUTH_ERR;
	}
	return PAM_SUCCESS;
}

static void
reset_tally(pam_handle_t *pamh, struct options *opts, int *fd)
{
	int rv;

	if (*fd == -1) {
		*fd = open_tally(opts->dir, opts->user, opts->uid, 1);
	}
	else {
		while ((rv=ftruncate(*fd, 0)) == -1 && errno == EINTR);
		if (rv == -1) {
			pam_syslog(pamh, LOG_ERR, "Error clearing the tally file for %s: %m", opts->user);
		}
	}
}

static int
write_tally(pam_handle_t *pamh, struct options *opts, struct tally_data *tallies, int *fd)
{
	struct tally *records;
	unsigned int i;
	int failures;
	unsigned int oldest;
	uint64_t oldtime;
	const void *source = NULL;

	if (*fd == -1) {
		*fd = open_tally(opts->dir, opts->user, opts->uid, 1);
	}
	if (*fd == -1) {
		if (errno == EACCES) {
			return PAM_SUCCESS;
		}
		pam_syslog(pamh, LOG_ERR, "Error opening the tally file for %s: %m", opts->user);
		return PAM_SYSTEM_ERR;
	}

	oldtime = 0;
	oldest = 0;
	failures = 0;

	for (i = 0; i < tallies->count; ++i) {
		if (tallies->records[i].time < oldtime) {
			oldtime = tallies->records[i].time;
			oldest = i;
		}
		if (opts->flags & FAILLOCK_FLAG_UNLOCKED ||
			opts->now - tallies->records[i].time >= opts->fail_interval ) {
			tallies->records[i].status &= ~TALLY_STATUS_VALID;
		} else {
			++failures;
		}
	}

	if (oldest >= tallies->count || (tallies->records[oldest].status & TALLY_STATUS_VALID)) {
		oldest = tallies->count;

		if ((records=realloc(tallies->records, (oldest+1) * sizeof (*tallies->records))) == NULL) {
			pam_syslog(pamh, LOG_CRIT, "Error allocating memory for tally records: %m");
			return PAM_BUF_ERR;
		}

		++tallies->count;
		tallies->records = records;
	}

	memset(&tallies->records[oldest], 0, sizeof (*tallies->records));

	tallies->records[oldest].status = TALLY_STATUS_VALID;
	if (pam_get_item(pamh, PAM_RHOST, &source) != PAM_SUCCESS || source == NULL) {
		if (pam_get_item(pamh, PAM_TTY, &source) != PAM_SUCCESS || source == NULL) {
			if (pam_get_item(pamh, PAM_SERVICE, &source) != PAM_SUCCESS || source == NULL) {
				source = "";
			}
		}
		else {
			tallies->records[oldest].status |= TALLY_STATUS_TTY;
		}
	}
	else {
		tallies->records[oldest].status |= TALLY_STATUS_RHOST;
	}

	strncpy(tallies->records[oldest].source, source, sizeof(tallies->records[oldest].source));
	/* source does not have to be null terminated */

	tallies->records[oldest].time = opts->now;

	++failures;

	if (opts->deny && failures == opts->deny) {
#ifdef HAVE_LIBAUDIT
		char buf[64];
		int audit_fd;

		audit_fd = audit_open();
		/* If there is an error & audit support is in the kernel report error */
		if ((audit_fd < 0) && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
			errno == EAFNOSUPPORT))
			return PAM_SYSTEM_ERR;

		snprintf(buf, sizeof(buf), "pam_faillock uid=%u ", opts->uid);
		audit_log_user_message(audit_fd, AUDIT_ANOM_LOGIN_FAILURES, buf,
			NULL, NULL, NULL, 1);

		if (!opts->is_admin || (opts->flags & FAILLOCK_FLAG_DENY_ROOT)) {
			audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_LOCK, buf,
				NULL, NULL, NULL, 1);
		}
		close(audit_fd);
#endif
		if (!(opts->flags & FAILLOCK_FLAG_NO_LOG_INFO)) {
			pam_syslog(pamh, LOG_INFO, "Consecutive login failures for user %s account temporarily locked",
				opts->user);
		}
	}

	if (update_tally(*fd, tallies) == 0)
		return PAM_SUCCESS;

	return PAM_SYSTEM_ERR;
}

static void
faillock_message(pam_handle_t *pamh, struct options *opts)
{
	int64_t left;

	if (!(opts->flags & FAILLOCK_FLAG_SILENT)) {
		if (opts->is_admin) {
			left = opts->latest_time + opts->root_unlock_time - opts->now;
		}
		else {
			left = opts->latest_time + opts->unlock_time - opts->now;
		}

		if (left > 0) {
			left = (left + 59)/60; /* minutes */

			pam_info(pamh, _("Account temporarily locked due to %d failed logins"),
				opts->failures);
			pam_info(pamh, _("(%d minutes left to unlock)"), (int)left);
		}
		else {
			pam_info(pamh, _("Account locked due to %d failed logins"),
				opts->failures);
		}
	}
}

static void
tally_cleanup(struct tally_data *tallies, int fd)
{
	if (fd != -1) {
		close(fd);
	}

	free(tallies->records);
}

static void
opts_cleanup(struct options *opts)
{
	free(opts->dir);
	free(opts->admin_group);
}

/*---------------------------------------------------------------------*/

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
		    int argc, const char **argv)
{
	struct options opts;
	int rv, fd = -1;
	struct tally_data tallies;

	memset(&tallies, 0, sizeof(tallies));

	args_parse(pamh, argc, argv, flags, &opts);
	if (opts.fatal_error) {
		rv = PAM_BUF_ERR;
		goto err;
	}

	pam_fail_delay(pamh, 2000000);	/* 2 sec delay for on failure */

	if ((rv=get_pam_user(pamh, &opts)) != PAM_SUCCESS) {
		goto err;
	}

	if (!(opts.flags & FAILLOCK_FLAG_LOCAL_ONLY) ||
		check_local_user (pamh, opts.user) != 0) {
		switch (opts.action) {
			case FAILLOCK_ACTION_PREAUTH:
				rv = check_tally(pamh, &opts, &tallies, &fd);
				if (rv == PAM_AUTH_ERR && !(opts.flags & FAILLOCK_FLAG_SILENT)) {
					faillock_message(pamh, &opts);
				}
				break;

			case FAILLOCK_ACTION_AUTHSUCC:
				rv = check_tally(pamh, &opts, &tallies, &fd);
				if (rv == PAM_SUCCESS) {
					reset_tally(pamh, &opts, &fd);
				}
				break;

			case FAILLOCK_ACTION_AUTHFAIL:
				rv = check_tally(pamh, &opts, &tallies, &fd);
				if (rv == PAM_SUCCESS) {
					rv = PAM_IGNORE; /* this return value should be ignored */
					write_tally(pamh, &opts, &tallies, &fd);
				}
				break;
		}
	}

	tally_cleanup(&tallies, fd);

err:
	opts_cleanup(&opts);

	return rv;
}

/*---------------------------------------------------------------------*/

PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
	       int argc UNUSED, const char **argv UNUSED)
{
	return PAM_SUCCESS;
}

/*---------------------------------------------------------------------*/

PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
		 int argc, const char **argv)
{
	struct options opts;
	int rv, fd = -1;
	struct tally_data tallies;

	memset(&tallies, 0, sizeof(tallies));

	args_parse(pamh, argc, argv, flags, &opts);

	if (opts.fatal_error) {
		rv = PAM_BUF_ERR;
		goto err;
	}

	opts.action = FAILLOCK_ACTION_AUTHSUCC;

	if ((rv=get_pam_user(pamh, &opts)) != PAM_SUCCESS) {
		goto err;
	}

	if (!(opts.flags & FAILLOCK_FLAG_LOCAL_ONLY) ||
		check_local_user (pamh, opts.user) != 0) {
		check_tally(pamh, &opts, &tallies, &fd); /* for auditing */
		reset_tally(pamh, &opts, &fd);
	}

	tally_cleanup(&tallies, fd);

err:
	opts_cleanup(&opts);

	return rv;
}

/*-----------------------------------------------------------------------*/

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_faillock_modstruct = {
     MODULE_NAME,
#ifdef PAM_SM_AUTH
     pam_sm_authenticate,
     pam_sm_setcred,
#else
     NULL,
     NULL,
#endif
#ifdef PAM_SM_ACCOUNT
     pam_sm_acct_mgmt,
#else
     NULL,
#endif
     NULL,
     NULL,
     NULL,
};

#endif   /* #ifdef PAM_STATIC */