/******************************************************************************
* A module for Linux-PAM that will set the default security context after login
* via PAM.
*
* Copyright (c) 2003-2008 Red Hat, Inc.
* Written by Dan Walsh <dwalsh@redhat.com>
* Additional improvements by Tomas Mraz <tmraz@redhat.com>
*
* 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 <errno.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#define PAM_SM_AUTH
#define PAM_SM_SESSION
#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>
#include <selinux/selinux.h>
#include <selinux/get_context_list.h>
#include <selinux/flask.h>
#include <selinux/av_permissions.h>
#include <selinux/selinux.h>
#include <selinux/context.h>
#include <selinux/get_default_type.h>
#ifdef HAVE_LIBAUDIT
#include <libaudit.h>
#include <sys/select.h>
#include <errno.h>
#endif
/* Send audit message */
static
int send_audit_message(pam_handle_t *pamh, int success, security_context_t default_context,
security_context_t selected_context)
{
int rc=0;
#ifdef HAVE_LIBAUDIT
char *msg = NULL;
int audit_fd = audit_open();
security_context_t default_raw=NULL;
security_context_t selected_raw=NULL;
const void *tty = NULL, *rhost = NULL;
rc = -1;
if (audit_fd < 0) {
if (errno == EINVAL || errno == EPROTONOSUPPORT ||
errno == EAFNOSUPPORT)
return 0; /* No audit support in kernel */
pam_syslog(pamh, LOG_ERR, "Error connecting to audit system.");
return rc;
}
(void)pam_get_item(pamh, PAM_TTY, &tty);
(void)pam_get_item(pamh, PAM_RHOST, &rhost);
if (selinux_trans_to_raw_context(default_context, &default_raw) < 0) {
pam_syslog(pamh, LOG_ERR, "Error translating default context.");
default_raw = NULL;
}
if (selinux_trans_to_raw_context(selected_context, &selected_raw) < 0) {
pam_syslog(pamh, LOG_ERR, "Error translating selected context.");
selected_raw = NULL;
}
if (asprintf(&msg, "pam: default-context=%s selected-context=%s",
default_raw ? default_raw : (default_context ? default_context : "?"),
selected_raw ? selected_raw : (selected_context ? selected_context : "?")) < 0) {
pam_syslog(pamh, LOG_ERR, "Error allocating memory.");
goto out;
}
if (audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
msg, rhost, NULL, tty, success) <= 0) {
pam_syslog(pamh, LOG_ERR, "Error sending audit message.");
goto out;
}
rc = 0;
out:
free(msg);
freecon(default_raw);
freecon(selected_raw);
close(audit_fd);
#else
pam_syslog(pamh, LOG_NOTICE, "pam: default-context=%s selected-context=%s success %d", default_context, selected_context, success);
#endif
return rc;
}
static int
send_text (pam_handle_t *pamh, const char *text, int debug)
{
if (debug)
pam_syslog(pamh, LOG_NOTICE, "%s", text);
return pam_info (pamh, "%s", text);
}
/*
* This function sends a message to the user and gets the response. The caller
* is responsible for freeing the responses.
*/
static int
query_response (pam_handle_t *pamh, const char *text, const char *def,
char **response, int debug)
{
int rc;
if (def)
rc = pam_prompt (pamh, PAM_PROMPT_ECHO_ON, response, "%s [%s] ", text, def);
else
rc = pam_prompt (pamh, PAM_PROMPT_ECHO_ON, response, "%s ", text);
if (*response == NULL) {
rc = PAM_CONV_ERR;
}
if (rc != PAM_SUCCESS) {
pam_syslog(pamh, LOG_WARNING, "No response to query: %s", text);
} else if (debug)
pam_syslog(pamh, LOG_NOTICE, "%s %s", text, *response);
return rc;
}
static int mls_range_allowed(pam_handle_t *pamh, security_context_t src, security_context_t dst, int debug)
{
struct av_decision avd;
int retval;
security_class_t class;
access_vector_t bit;
context_t src_context;
context_t dst_context;
class = string_to_security_class("context");
if (!class) {
pam_syslog(pamh, LOG_ERR, "Failed to translate security class context. %m");
return 0;
}
bit = string_to_av_perm(class, "contains");
if (!bit) {
pam_syslog(pamh, LOG_ERR, "Failed to translate av perm contains. %m");
return 0;
}
src_context = context_new (src);
dst_context = context_new (dst);
context_range_set(dst_context, context_range_get(src_context));
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Checking if %s mls range valid for %s", dst, context_str(dst_context));
retval = security_compute_av(context_str(dst_context), dst, class, bit, &avd);
context_free(src_context);
context_free(dst_context);
if (retval || ((bit & avd.allowed) != bit))
return 0;
return 1;
}
static security_context_t
config_context (pam_handle_t *pamh, security_context_t defaultcon, int use_current_range, int debug)
{
security_context_t newcon=NULL;
context_t new_context;
int mls_enabled = is_selinux_mls_enabled();
char *response=NULL;
char *type=NULL;
char resp_val = 0;
pam_prompt (pamh, PAM_TEXT_INFO, NULL, _("Default Security Context %s\n"), defaultcon);
while (1) {
if (query_response(pamh,
_("Would you like to enter a different role or level?"), "n",
&response, debug) == PAM_SUCCESS) {
resp_val = response[0];
_pam_drop(response);
} else {
resp_val = 'N';
}
if ((resp_val == 'y') || (resp_val == 'Y'))
{
if ((new_context = context_new(defaultcon)) == NULL)
goto fail_set;
/* Allow the user to enter role and level individually */
if (query_response(pamh, _("role:"), context_role_get(new_context),
&response, debug) == PAM_SUCCESS && response[0]) {
if (get_default_type(response, &type)) {
pam_prompt (pamh, PAM_ERROR_MSG, NULL, _("No default type for role %s\n"), response);
_pam_drop(response);
continue;
} else {
if (context_role_set(new_context, response))
goto fail_set;
if (context_type_set (new_context, type))
goto fail_set;
_pam_drop(type);
}
}
_pam_drop(response);
if (mls_enabled)
{
if (use_current_range) {
security_context_t mycon = NULL;
context_t my_context;
if (getcon(&mycon) != 0)
goto fail_set;
my_context = context_new(mycon);
if (my_context == NULL) {
freecon(mycon);
goto fail_set;
}
freecon(mycon);
if (context_range_set(new_context, context_range_get(my_context))) {
context_free(my_context);
goto fail_set;
}
context_free(my_context);
} else if (query_response(pamh, _("level:"), context_range_get(new_context),
&response, debug) == PAM_SUCCESS && response[0]) {
if (context_range_set(new_context, response))
goto fail_set;
}
_pam_drop(response);
}
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Selected Security Context %s", context_str(new_context));
/* Get the string value of the context and see if it is valid. */
if (!security_check_context(context_str(new_context))) {
newcon = strdup(context_str(new_context));
if (newcon == NULL)
goto fail_set;
context_free(new_context);
/* we have to check that this user is allowed to go into the
range they have specified ... role is tied to an seuser, so that'll
be checked at setexeccon time */
if (mls_enabled && !mls_range_allowed(pamh, defaultcon, newcon, debug)) {
pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", defaultcon, newcon);
send_audit_message(pamh, 0, defaultcon, newcon);
free(newcon);
goto fail_range;
}
return newcon;
}
else {
send_audit_message(pamh, 0, defaultcon, context_str(new_context));
send_text(pamh,_("Not a valid security context"),debug);
}
context_free(new_context); /* next time around allocates another */
}
else
return strdup(defaultcon);
} /* end while */
return NULL;
fail_set:
free(type);
_pam_drop(response);
context_free (new_context);
send_audit_message(pamh, 0, defaultcon, NULL);
fail_range:
return NULL;
}
static security_context_t
context_from_env (pam_handle_t *pamh, security_context_t defaultcon, int env_params, int use_current_range, int debug)
{
security_context_t newcon = NULL;
context_t new_context;
context_t my_context = NULL;
int mls_enabled = is_selinux_mls_enabled();
const char *env = NULL;
char *type = NULL;
int fail = 1;
if ((new_context = context_new(defaultcon)) == NULL)
goto fail_set;
if (env_params && (env = pam_getenv(pamh, "SELINUX_ROLE_REQUESTED")) != NULL && env[0] != '\0') {
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Requested role: %s", env);
if (get_default_type(env, &type)) {
pam_syslog(pamh, LOG_NOTICE, "No default type for role %s", env);
goto fail_set;
} else {
if (context_role_set(new_context, env))
goto fail_set;
if (context_type_set(new_context, type))
goto fail_set;
}
}
if (mls_enabled) {
if ((env = pam_getenv(pamh, "SELINUX_USE_CURRENT_RANGE")) != NULL && env[0] == '1') {
if (debug)
pam_syslog(pamh, LOG_NOTICE, "SELINUX_USE_CURRENT_RANGE is set");
use_current_range = 1;
}
if (use_current_range) {
security_context_t mycon = NULL;
if (getcon(&mycon) != 0)
goto fail_set;
my_context = context_new(mycon);
if (my_context == NULL) {
freecon(mycon);
goto fail_set;
}
freecon(mycon);
env = context_range_get(my_context);
} else {
env = pam_getenv(pamh, "SELINUX_LEVEL_REQUESTED");
}
if (env != NULL && env[0] != '\0') {
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Requested level: %s", env);
if (context_range_set(new_context, env))
goto fail_set;
}
}
newcon = strdup(context_str(new_context));
if (newcon == NULL)
goto fail_set;
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Selected Security Context %s", newcon);
/* Get the string value of the context and see if it is valid. */
if (security_check_context(newcon)) {
pam_syslog(pamh, LOG_NOTICE, "Not a valid security context %s", newcon);
goto fail_set;
}
/* we have to check that this user is allowed to go into the
range they have specified ... role is tied to an seuser, so that'll
be checked at setexeccon time */
if (mls_enabled && !mls_range_allowed(pamh, defaultcon, newcon, debug)) {
pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", defaultcon, newcon);
goto fail_set;
}
fail = 0;
fail_set:
free(type);
context_free(my_context);
context_free(new_context);
if (fail) {
send_audit_message(pamh, 0, defaultcon, newcon);
freecon(newcon);
newcon = NULL;
}
return newcon;
}
#define DATANAME "pam_selinux_context"
typedef struct {
security_context_t exec_context;
security_context_t prev_exec_context;
security_context_t default_user_context;
security_context_t tty_context;
security_context_t prev_tty_context;
char *tty_path;
} module_data_t;
static void
free_module_data(module_data_t *data)
{
free(data->tty_path);
freecon(data->prev_tty_context);
freecon(data->tty_context);
freecon(data->default_user_context);
freecon(data->prev_exec_context);
if (data->exec_context != data->default_user_context)
freecon(data->exec_context);
memset(data, 0, sizeof(*data));
free(data);
}
static void
cleanup(pam_handle_t *pamh UNUSED, void *data, int err UNUSED)
{
free_module_data(data);
}
static const module_data_t *
get_module_data(const pam_handle_t *pamh)
{
const void *data;
return (pam_get_data(pamh, DATANAME, &data) == PAM_SUCCESS) ? data : NULL;
}
static const char *
get_item(const pam_handle_t *pamh, int item_type)
{
const void *item;
return (pam_get_item(pamh, item_type, &item) == PAM_SUCCESS) ? item : NULL;
}
static int
set_exec_context(const pam_handle_t *pamh, security_context_t context)
{
if (setexeccon(context) == 0)
return 0;
pam_syslog(pamh, LOG_ERR, "Setting executable context \"%s\" failed: %m",
context ? context : "");
return -1;
}
static int
set_file_context(const pam_handle_t *pamh, security_context_t context,
const char *file)
{
if (!file)
return 0;
if (setfilecon(file, context) == 0 || errno == ENOENT)
return 0;
pam_syslog(pamh, LOG_ERR, "Setting file context \"%s\" failed for %s: %m",
context ? context : "", file);
return -1;
}
static int
compute_exec_context(pam_handle_t *pamh, module_data_t *data,
int select_context, int use_current_range,
int env_params, int debug)
{
const char *username;
#ifdef HAVE_GETSEUSER
const char *service;
#endif
char *seuser = NULL;
char *level = NULL;
security_context_t *contextlist = NULL;
int num_contexts = 0;
const struct passwd *pwd;
if (!(username = get_item(pamh, PAM_USER))) {
pam_syslog(pamh, LOG_ERR, "Cannot obtain the user name");
return PAM_USER_UNKNOWN;
}
if ((pwd = pam_modutil_getpwnam(pamh, username)) != NULL) {
username = pwd->pw_name;
} /* ignore error and keep using original username */
/* compute execute context */
#ifdef HAVE_GETSEUSER
if (!(service = get_item(pamh, PAM_SERVICE))) {
pam_syslog(pamh, LOG_ERR, "Cannot obtain the service name");
return PAM_SESSION_ERR;
}
if (getseuser(username, service, &seuser, &level) == 0) {
#else
if (getseuserbyname(username, &seuser, &level) == 0) {
#endif
num_contexts = get_ordered_context_list_with_level(seuser, level, NULL,
&contextlist);
if (debug)
pam_syslog(pamh, LOG_DEBUG, "Username= %s SELinux User= %s Level= %s",
username, seuser, level);
free(level);
}
if (num_contexts > 0) {
free(seuser);
data->default_user_context = strdup(contextlist[0]);
freeconary(contextlist);
if (!data->default_user_context) {
pam_syslog(pamh, LOG_CRIT, "Out of memory");
return PAM_BUF_ERR;
}
data->exec_context = data->default_user_context;
if (select_context)
data->exec_context = config_context(pamh, data->default_user_context,
use_current_range, debug);
else if (env_params || use_current_range)
data->exec_context = context_from_env(pamh, data->default_user_context,
env_params, use_current_range,
debug);
}
if (!data->exec_context) {
pam_syslog(pamh, LOG_ERR, "Unable to get valid context for %s", username);
pam_prompt(pamh, PAM_ERROR_MSG, NULL,
_("Unable to get valid context for %s"), username);
}
if (getexeccon(&data->prev_exec_context) < 0)
data->prev_exec_context = NULL;
return PAM_SUCCESS;
}
static int
compute_tty_context(const pam_handle_t *pamh, module_data_t *data)
{
const char *tty = get_item(pamh, PAM_TTY);
if (!tty || !*tty || !strcmp(tty, "ssh") || !strncmp(tty, "NODEV", 5)) {
tty = ttyname(STDIN_FILENO);
if (!tty || !*tty)
tty = ttyname(STDOUT_FILENO);
if (!tty || !*tty)
tty = ttyname(STDERR_FILENO);
if (!tty || !*tty)
return PAM_SUCCESS;
}
if (strncmp("/dev/", tty, 5)) {
if (asprintf(&data->tty_path, "%s%s", "/dev/", tty) < 0)
data->tty_path = NULL;
} else {
data->tty_path = strdup(tty);
}
if (!data->tty_path) {
pam_syslog(pamh, LOG_CRIT, "Out of memory");
return PAM_BUF_ERR;
}
if (getfilecon(data->tty_path, &data->prev_tty_context) < 0) {
data->prev_tty_context = NULL;
if (errno == ENOENT) {
free(data->tty_path);
data->tty_path = NULL;
return PAM_SUCCESS;
}
pam_syslog(pamh, LOG_ERR, "Failed to get current context for %s: %m",
data->tty_path);
return (security_getenforce() == 1) ? PAM_SESSION_ERR : PAM_SUCCESS;
}
if (security_compute_relabel(data->exec_context, data->prev_tty_context,
SECCLASS_CHR_FILE, &data->tty_context)) {
data->tty_context = NULL;
pam_syslog(pamh, LOG_ERR, "Failed to compute new context for %s: %m",
data->tty_path);
freecon(data->prev_tty_context);
data->prev_tty_context = NULL;
free(data->tty_path);
data->tty_path = NULL;
return (security_getenforce() == 1) ? PAM_SESSION_ERR : PAM_SUCCESS;
}
return PAM_SUCCESS;
}
static int
restore_context(const pam_handle_t *pamh, const module_data_t *data, int debug)
{
int err;
if (!data) {
if (debug)
pam_syslog(pamh, LOG_NOTICE, "No context to restore");
return PAM_SUCCESS;
}
if (debug && data->tty_path)
pam_syslog(pamh, LOG_NOTICE,
"Restore file context of tty %s: [%s] -> [%s]",
data->tty_path,
data->tty_context ? data->tty_context : "",
data->prev_tty_context ? data->prev_tty_context : "");
err = set_file_context(pamh, data->prev_tty_context, data->tty_path);
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Restore executable context: [%s] -> [%s]",
data->exec_context,
data->prev_exec_context ? data->prev_exec_context : "");
err |= set_exec_context(pamh, data->prev_exec_context);
if (err && security_getenforce() == 1)
return PAM_SESSION_ERR;
return PAM_SUCCESS;
}
static int
set_context(pam_handle_t *pamh, const module_data_t *data,
int debug, int verbose)
{
int rc, err;
if (debug && data->tty_path)
pam_syslog(pamh, LOG_NOTICE, "Set file context of tty %s: [%s] -> [%s]",
data->tty_path,
data->prev_tty_context ? data->prev_tty_context : "",
data->tty_context ? data->tty_context : "");
err = set_file_context(pamh, data->tty_context, data->tty_path);
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Set executable context: [%s] -> [%s]",
data->prev_exec_context ? data->prev_exec_context : "",
data->exec_context);
rc = set_exec_context(pamh, data->exec_context);
err |= rc;
send_audit_message(pamh, !rc, data->default_user_context, data->exec_context);
if (verbose && !rc) {
char msg[PATH_MAX];
snprintf(msg, sizeof(msg),
_("Security Context %s Assigned"), data->exec_context);
send_text(pamh, msg, debug);
}
#ifdef HAVE_SETKEYCREATECON
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Set key creation context to %s",
data->exec_context ? data->exec_context : "");
rc = setkeycreatecon(data->exec_context);
err |= rc;
if (rc)
pam_syslog(pamh, LOG_ERR, "Setting key creation context %s failed: %m",
data->exec_context ? data->exec_context : "");
if (verbose && !rc) {
char msg[PATH_MAX];
snprintf(msg, sizeof(msg),
_("Key Creation Context %s Assigned"), data->exec_context);
send_text(pamh, msg, debug);
}
#endif
if (err && security_getenforce() == 1)
return PAM_SESSION_ERR;
return PAM_SUCCESS;
}
static int
create_context(pam_handle_t *pamh, int argc, const char **argv,
int debug, int verbose)
{
int i;
int ttys = 1;
int select_context = 0;
int use_current_range = 0;
int env_params = 0;
module_data_t *data;
/* Parse arguments. */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "nottys") == 0) {
ttys = 0;
}
if (strcmp(argv[i], "select_context") == 0) {
select_context = 1;
}
if (strcmp(argv[i], "use_current_range") == 0) {
use_current_range = 1;
}
if (strcmp(argv[i], "env_params") == 0) {
env_params = 1;
}
}
if (is_selinux_enabled() <= 0) {
if (debug)
pam_syslog(pamh, LOG_NOTICE, "SELinux is not enabled");
return PAM_SUCCESS;
}
if (select_context && env_params) {
pam_syslog(pamh, LOG_ERR,
"select_context cannot be used with env_params");
select_context = 0;
}
if (!(data = calloc(1, sizeof(*data)))) {
pam_syslog(pamh, LOG_CRIT, "Out of memory");
return PAM_BUF_ERR;
}
i = compute_exec_context(pamh, data, select_context, use_current_range,
env_params, debug);
if (i != PAM_SUCCESS) {
free_module_data(data);
return i;
}
if (!data->exec_context) {
free_module_data(data);
return (security_getenforce() == 1) ? PAM_SESSION_ERR : PAM_SUCCESS;
}
if (ttys && (i = compute_tty_context(pamh, data)) != PAM_SUCCESS) {
free_module_data(data);
return i;
}
if ((i = pam_set_data(pamh, DATANAME, data, cleanup)) != PAM_SUCCESS) {
pam_syslog(pamh, LOG_ERR, "Error saving context: %m");
free_module_data(data);
return i;
}
return set_context(pamh, data, debug, verbose);
}
int
pam_sm_authenticate(pam_handle_t *pamh UNUSED, int flags UNUSED,
int argc UNUSED, const char **argv UNUSED)
{
/* Fail by default. */
return PAM_AUTH_ERR;
}
int
pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
int argc UNUSED, const char **argv UNUSED)
{
return PAM_SUCCESS;
}
int
pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
int argc, const char **argv)
{
const module_data_t *data;
int i, debug = 0, verbose = 0, close_session = 0, restore = 0;
/* Parse arguments. */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) {
debug = 1;
}
if (strcmp(argv[i], "verbose") == 0) {
verbose = 1;
}
if (strcmp(argv[i], "close") == 0) {
close_session = 1;
}
if (strcmp(argv[i], "restore") == 0) {
restore = 1;
}
}
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Open Session");
/* Is this module supposed to execute close_session only? */
if (close_session)
return PAM_SUCCESS;
data = get_module_data(pamh);
/* Is this module supposed only to restore original context? */
if (restore)
return restore_context(pamh, data, debug);
/* If there is a saved context, this module is supposed to set it again. */
return data ? set_context(pamh, data, debug, verbose) :
create_context(pamh, argc, argv, debug, verbose);
}
int
pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
int argc, const char **argv)
{
int i, debug = 0, open_session = 0;
/* Parse arguments. */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) {
debug = 1;
}
if (strcmp(argv[i], "open") == 0) {
open_session = 1;
}
}
if (debug)
pam_syslog(pamh, LOG_NOTICE, "Close Session");
/* Is this module supposed to execute open_session only? */
if (open_session)
return PAM_SUCCESS;
/* Restore original context. */
return restore_context(pamh, get_module_data(pamh), debug);
}