Index: modules/pam_exec/pam_exec.c =================================================================== RCS file: /cvsroot/pam/Linux-PAM/modules/pam_exec/pam_exec.c,v retrieving revision 1.2 diff -u -r1.2 pam_exec.c --- modules/pam_exec/pam_exec.c 17 Jun 2006 16:44:58 -0000 1.2 +++ modules/pam_exec/pam_exec.c 30 Jul 2006 08:58:33 -0000 @@ -1,5 +1,7 @@ -/* +/* pam_exec.c - invoke an external program as part of a PAM stack + * * Copyright (c) 2006 Thorsten Kukuk + * Copyright (c) 2006 Daniel Richard G. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -33,252 +35,1490 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#if defined(HAVE_CONFIG_H) -#include "config.h" +/* Solaris needs this bit to give us the correct prototypes for + * get{pwnam,pwuid,grnam,grgid}_r() + */ +#ifdef sun +# define _POSIX_PTHREAD_SEMANTICS +#endif + +#ifdef HAVE_CONFIG_H +# include #endif -#include -#include -#include #include +#include +#include #include +#include +#include +#include +#include #include -#include -#include -#include -#include #include +#include +#include +#include +#include - +/* Activate the appropriate bits in the PAM headers + */ #define PAM_SM_AUTH #define PAM_SM_ACCOUNT #define PAM_SM_SESSION #define PAM_SM_PASSWORD +#include /* needed on Solaris */ #include -#include -#include +/* File descriptor for the setenv channel + */ +#define SETENV_FILENO 3 + +#if SETENV_FILENO <= 2 || SETENV_FILENO >= FD_SETSIZE +# error SETENV_FILENO value is outside of legal range +#endif + +/* These many bytes are allocated on the stack, so don't push it + */ +#define LINEBUF_SIZE 256 + +/* Workaround for Solaris + */ +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif + +typedef enum +{ + PE_AUTHENTICATE, + /* PE_SETCRED, */ + PE_ACCT_MGMT, + PE_OPEN_SESSION, + PE_CLOSE_SESSION, + PE_CHAUTHTOK +} +PamExecAction; + +struct exec_options +{ + char *command; /* sh-compatible command string */ + + const char *runas_spec; /* chown(1)-like user/group spec */ + uid_t runas_uid; /* Run command as this user... */ + gid_t runas_gid; /* ...and this group */ + + int flags; /* Raw PAM flags */ + + int allow_authtok; /* boolean */ + int allow_setenv; /* boolean */ + int debug; /* boolean */ + int expose_account; /* boolean */ + int fail_delay; /* integer (milliseconds) */ + int maxtime; /* integer (seconds) */ + int on_open; /* boolean */ + int on_close; /* boolean */ + int silent; /* boolean */ + int warnings; /* boolean */ + int try_first_pass; /* boolean */ + int use_first_pass; /* boolean */ + /* int use_mapped_pass; / * boolean */ +}; + +/* Work around any built-in log() math function + */ +#undef log +#define log my_log_fn + +static void +log(int err, const char *format, ...) +{ + va_list args; + + openlog("pam_exec", LOG_CONS | LOG_PID, LOG_AUTHPRIV); + + va_start(args, format); + vsyslog(LOG_AUTHPRIV | err, format, args); + va_end(args); + + closelog(); +} + +/* Returns true iff the argument is a numeric string + */ +static int +is_numeric(const char *s) +{ + int i; + + if (s == NULL || *s == '\0') + return 0; + + for (i = 0; s[i] != '\0'; i++) + if (! isdigit(s[i])) + return 0; + + return 1; +} + +/* Returns true iff fd is an open file descriptor + */ +static int +is_open_fd(int fd) +{ + if (fd < 0) + return 0; + + return fcntl(fd, F_GETFD) != -1; +} + +/* Returns true iff the given string s, in a buffer of size bufsize, + * consists entirely of printable characters and is properly + * null-terminated. + */ +static int +is_safe_string(const char *s, size_t bufsize) +{ + size_t i; + + if (s == NULL || bufsize == 0) + return 0; + + for (i = 0; i < bufsize && s[i] != '\0'; i++) + if (! isprint(s[i])) + return 0; + + /* If (i == bufsize) then there is no terminating null + */ + return i < bufsize; +} + +/* Reads a line of text from the given stream, strips off the trailing + * newline / carriage return, checks that it is clean, and stores it in the + * buffer linebuf with the given size. Returns false if the stream is at + * end-of-file, if an error occurred, if the line was unclean or if it was + * too long to fit into the buffer; true otherwise. + */ +static int +get_line(struct exec_options *options, + char *linebuf, + size_t size, + FILE *stream) +{ + int too_long = 0; + int safe; + char *nl; + + if (options == NULL || + linebuf == NULL || + size == 0 || + stream == NULL) + { + return 0; + } + + for (;;) + { + if (fgets(linebuf, (int)size, stream) == NULL) + { + linebuf[0] = '\0'; + return 0; + } + + nl = strchr(linebuf, '\n'); + if (nl != NULL) + break; + + too_long = 1; + } + + /* Chop off the trailing newline / carriage return + */ + *nl = '\0'; + if (nl != linebuf && *(nl - 1) == '\r') + *(nl - 1) = '\0'; + + safe = is_safe_string(linebuf, size); + + if (options->debug) + log(LOG_DEBUG, "Read a line of length %d from fd %d", strlen(linebuf), fileno(stream)); + + if (options->warnings && ! safe) + log(LOG_WARNING, "Command generated output with unsafe characters"); + + if (options->warnings && too_long) + log(LOG_WARNING, "Command generated an overlong line"); + + if (! safe || too_long) + linebuf[0] = '\0'; /* for safety */ + + return safe && ! too_long; +} + +/* Parses a chown(1)-like user/group spec, of the form "user:group" or + * "user" or ":group". The associated user ID and/or group ID are stored + * into *uid_ptr and *gid_ptr. If the group is not specified, then the + * user's primary group is returned. If the user is not specified, then no + * user ID is returned. The user and group can be specified either by name, + * or by the numeric ID in string form. Returns a PAM error code. + */ +static int +parse_usergroup_spec(const char *spec, uid_t *uid_ptr, gid_t *gid_ptr) +{ + int status = PAM_SUCCESS; + char *spec_copy; + const char *user; + const char *group = NULL; + size_t pw_buflen; + char *pw_buf = NULL; + struct passwd pw, *pw_ptr; + size_t gr_buflen; + char *gr_buf = NULL; + struct group gr, *gr_ptr; + int ret; + + if (spec == NULL || *spec == '\0') + return PAM_SERVICE_ERR; + + spec_copy = strdup(spec); + if (spec_copy == NULL) + { + status = PAM_BUF_ERR; + goto cleanup; + } + + user = spec_copy; + + { + char *sep = strchr(spec_copy, ':'); + + if (sep != NULL) + { + *sep = '\0'; + group = sep + 1; + } + } + + if (*user != '\0') + { + /* Look up user ID/name */ + + pw_buflen = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX); + pw_buf = (char *)malloc(pw_buflen); + if (pw_buf == NULL) + { + status = PAM_BUF_ERR; + goto cleanup; + } + + if (is_numeric(user)) + { + uid_t id = (uid_t)atoi(user); + + ret = getpwuid_r(id, + &pw, + pw_buf, + pw_buflen, + &pw_ptr); + } + else + ret = getpwnam_r(user, + &pw, + pw_buf, + pw_buflen, + &pw_ptr); + + if (ret == 0 && pw_ptr == &pw) + { + *uid_ptr = pw.pw_uid; + *gid_ptr = pw.pw_gid; + } + else + { + log(LOG_ERR, "Unable to get passwd info for user \"%s\"", user); + status = PAM_SYSTEM_ERR; + } + } + + if (group != NULL && *group != '\0') + { + /* Look up group name */ + + gr_buflen = (size_t)sysconf(_SC_GETGR_R_SIZE_MAX); + gr_buf = (char *)malloc(gr_buflen); + if (gr_buf == NULL) + { + status = PAM_BUF_ERR; + goto cleanup; + } + + if (is_numeric(group)) + { + gid_t id = (gid_t)atoi(group); + + ret = getgrgid_r(id, + &gr, + gr_buf, + gr_buflen, + &gr_ptr); + } + else + ret = getgrnam_r(group, + &gr, + gr_buf, + gr_buflen, + &gr_ptr); + + if (ret == 0 && gr_ptr == &gr) + { + *gid_ptr = gr.gr_gid; + } + else + { + log(LOG_ERR, "Unable to get info for group \"%s\"", group); + status = PAM_SYSTEM_ERR; + } + } + + cleanup: + + if (gr_buf != NULL) + free(gr_buf); + + if (pw_buf != NULL) + free(pw_buf); + + if (spec_copy != NULL) + free(spec_copy); + + return status; +} + +/* Parses the "command-line" options passed to the module in the PAM + * configuration file, as well as the flags variable, and sets the fields + * of the exec_options struct accordingly. Returns a PAM error code. + */ +static int +parse_options(struct exec_options *options, + int flags, + int argc, + const char **argv, + PamExecAction action) +{ + size_t cmd_len = 0; + int cmd_i0; + int i; + + memset(options, 0, sizeof(struct exec_options)); + + /* Set the non-zero/non-NULL defaults + */ + options->runas_uid = (uid_t)-1; + options->runas_gid = (gid_t)-1; + options->flags = flags; + options->on_open = 1; + options->on_close = 1; + options->warnings = 1; + + if (flags & PAM_SILENT) + options->silent = 1; + + for (i = 0; i < argc; i++) + { + const char *arg; + const char *value; + int inapplicable_option = 0; + + arg = argv[i]; + + value = strchr(arg, '='); + if (value != NULL) + ++value; + +#define MATCH_KEY(s) (strcmp(s, arg) == 0) +#define MATCH_KEY_VAL(s) (strncmp(s "=", arg, sizeof(s "=") - 1) == 0) + + if (MATCH_KEY("authtok")) + { + if (action == PE_AUTHENTICATE || + action == PE_CHAUTHTOK) + { + options->allow_authtok = 1; + } + else + inapplicable_option = 1; + } + else if (MATCH_KEY("debug")) + { + options->debug = 1; + } + else if (MATCH_KEY("exec")) + { + i++; + break; + } + else if (MATCH_KEY("expose_account")) + { + options->expose_account = 1; + } +#ifdef PAM_FAIL_DELAY + else if (MATCH_KEY_VAL("fail_delay")) + { + if (action == PE_AUTHENTICATE) + options->fail_delay = atoi(value); + else + inapplicable_option = 1; + } +#endif + else if (MATCH_KEY_VAL("maxtime")) + { + /* options->maxtime = atoi(value); */ + } + else if (MATCH_KEY("no_warn")) + { + options->warnings = 0; + } + else if (MATCH_KEY("on_close_only")) + { + if (action == PE_OPEN_SESSION || + action == PE_CLOSE_SESSION) + { + options->on_open = 0; + } + else + inapplicable_option = 1; + } + else if (MATCH_KEY("on_open_only")) + { + if (action == PE_OPEN_SESSION || + action == PE_CLOSE_SESSION) + { + options->on_close = 0; + } + else + inapplicable_option = 1; + } + else if (MATCH_KEY_VAL("runas")) + { + options->runas_spec = value; + } + else if (MATCH_KEY("setenv")) + { + options->allow_setenv = 1; + } + else if (MATCH_KEY("try_first_pass")) + { + options->try_first_pass = 1; + } + else if (MATCH_KEY("use_first_pass")) + { + options->use_first_pass = 1; + } + else if (MATCH_KEY("use_mapped_pass")) + { + /* options->use_mapped_pass = 1; */ + } + else + { + log(LOG_ERR, "Unrecognized option: %s", arg); + return PAM_SERVICE_ERR; + } +#undef MATCH_KEY +#undef MATCH_KEY_VAL + + if (inapplicable_option) + { + log(LOG_ERR, "Option not applicable: %s", arg); + return PAM_SERVICE_ERR; + } + } + + if (i == argc) + { + log(LOG_ERR, "No command was specified"); + return PAM_SERVICE_ERR; + } + + /* Some sanity checking */ + + if (options->fail_delay < 0) + options->fail_delay = 0; + + /* if (options->maxtime < 0) + options->maxtime = 0; */ + + if (! options->on_open && ! options->on_close) + { + log(LOG_ERR, "The on_open_only and on_close_only options are mutually incompatible"); + return PAM_SERVICE_ERR; + } + + /* Assemble the remaining args into a command string */ + + cmd_i0 = i; + + for (i = cmd_i0; i < argc; i++) + cmd_len += strlen(argv[i]) + 1; + + options->command = (char *)malloc(cmd_len); + options->command[0] = '\0'; + + for (i = cmd_i0; i < argc; i++) + { + /* O(n^2), but no one really cared... */ + + if (i > cmd_i0) + strcat(options->command, " "); + + strcat(options->command, argv[i]); + } + + /* Parse the argument to the "runas" option, if given + */ + if (options->runas_spec != NULL) + { + int ret; + + ret = parse_usergroup_spec(options->runas_spec, + &options->runas_uid, + &options->runas_gid); + if (ret != PAM_SUCCESS) + return ret; + + if (options->debug) + log(LOG_DEBUG, "Command will be executed with uid=%d gid=%d", (int)options->runas_uid, (int)options->runas_gid); + } + + return PAM_SUCCESS; +} + +/* Reads the command's stdout, and forwards it to the PAM conversation + * function so that the user can see it. Returns non-zero on error. + */ +static int +read_command_stdout(pam_handle_t *pamh, + struct exec_options *options, + FILE *stream) +{ + char linebuf[LINEBUF_SIZE]; + struct pam_conv *conv; + struct pam_message msg; + const struct pam_message *msg_ptrs[1]; + struct pam_response *resp; + int ret; + + ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv); + if (ret != PAM_SUCCESS) + return 1; + + msg.msg_style = PAM_TEXT_INFO; + msg.msg = linebuf; + msg_ptrs[0] = &msg; + + while (get_line(options, linebuf, sizeof(linebuf), stream)) + { + if (options->silent) + continue; + + resp = NULL; + + ret = conv->conv(1, + msg_ptrs, + &resp, + conv->appdata_ptr); + if (ret != PAM_SUCCESS) + return 1; + } + + return 0; +} + +/* Reads the command's stderr, and logs it. Returns zero. + */ +static int +read_command_stderr(pam_handle_t *pamh, + struct exec_options *options, + FILE *stream) +{ + char linebuf[LINEBUF_SIZE]; + + pamh = pamh; /* unused */ + + while (get_line(options, linebuf, sizeof(linebuf), stream)) + log(LOG_INFO, "> %s", linebuf); + + return 0; +} + +/* Reads the command's setenv channel, and if the options allow it, sets or + * unsets an environment variable as directed. Returns non-zero on error. + */ static int -call_exec (pam_handle_t *pamh, int argc, const char **argv) +read_command_setenv(pam_handle_t *pamh, + struct exec_options *options, + FILE *stream) { - int debug = 0; - int call_setuid = 0; - int optargc; - const char *logfile = NULL; - pid_t pid; - - if (argc < 1) { - pam_syslog (pamh, LOG_ERR, - "This module needs at least one argument"); - return PAM_SERVICE_ERR; - } - - for (optargc = 0; optargc < argc; optargc++) - { - if (argv[optargc][0] == '/') /* paths starts with / */ - break; - - if (strcasecmp (argv[optargc], "debug") == 0) - debug = 1; - else if (strncasecmp (argv[optargc], "log=", 4) == 0) - logfile = &argv[optargc][4]; - else if (strcasecmp (argv[optargc], "seteuid") == 0) - call_setuid = 1; - else - break; /* Unknown option, assume program to execute. */ - } - - - if (optargc >= argc) { - pam_syslog (pamh, LOG_ERR, "No path given as argument"); - return PAM_SERVICE_ERR; - } - - pid = fork(); - if (pid == -1) - return PAM_SYSTEM_ERR; - if (pid > 0) /* parent */ - { - int status = 0; - pid_t retval; - while ((retval = waitpid (pid, &status, 0)) == -1 && - errno == EINTR); - if (retval == (pid_t)-1) - { - pam_syslog (pamh, LOG_ERR, "waitpid returns with -1: %m"); - return PAM_SYSTEM_ERR; - } - else if (status != 0) - { - if (WIFEXITED(status)) - { - pam_syslog (pamh, LOG_ERR, "%s failed: exit code %d", - argv[optargc], WEXITSTATUS(status)); - pam_error (pamh, _("%s failed: exit code %d"), - argv[optargc], WEXITSTATUS(status)); - } - else if (WIFSIGNALED(status)) - { - pam_syslog (pamh, LOG_ERR, "%s failed: caught signal %d%s", - argv[optargc], WTERMSIG(status), - WCOREDUMP(status) ? " (core dumped)" : ""); - pam_error (pamh, _("%s failed: caught signal %d%s"), - argv[optargc], WTERMSIG(status), - WCOREDUMP(status) ? " (core dumped)" : ""); - } - else - { - pam_syslog (pamh, LOG_ERR, "%s failed: unknown status 0x%x", - argv[optargc], status); - pam_error (pamh, _("%s failed: unknown status 0x%x"), - argv[optargc], status); - } - return PAM_SYSTEM_ERR; - } - return PAM_SUCCESS; - } - else /* child */ - { - char **arggv; - int i; - - for (i = 0; i < sysconf (_SC_OPEN_MAX); i++) - close (i); - - /* New stdin. */ - if ((i = open ("/dev/null", O_RDWR)) < 0) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "open of /dev/null failed: %m"); - exit (err); - } - /* New stdout and stderr. */ - if (logfile) - { - time_t tm = time (NULL); - char *buffer = NULL; - - if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY)) == -1) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "open of %s failed: %m", - logfile); - exit (err); - } - if (asprintf (&buffer, "*** %s", ctime (&tm)) > 0) - { - pam_modutil_write (i, buffer, strlen (buffer)); - free (buffer); - } - } - else - if (dup (i) == -1) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "dup failed: %m"); - exit (err); - } - if (dup (i) == -1) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "dup failed: %m"); - exit (err); - } - - if (call_setuid) - if (setuid (geteuid ()) == -1) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m", - (unsigned long) geteuid ()); - exit (err); - } - - if (setsid () == -1) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "setsid failed: %m"); - exit (err); - } - - arggv = calloc (argc + 4, sizeof (char *)); - if (arggv == NULL) - exit (ENOMEM); - - for (i = 0; i < (argc - optargc); i++) - arggv[i] = argv[i+optargc]; - arggv[i] = NULL; - - if (debug) - pam_syslog (pamh, LOG_DEBUG, "Calling %s ...", arggv[0]); - - if (execv (arggv[0], arggv) == -1) - { - int err = errno; - pam_syslog (pamh, LOG_ERR, "execv(%s,...) failed: %m", - arggv[0]); - exit (err); - } - exit (1); /* should never be reached. */ - } - return PAM_SYSTEM_ERR; + int status = 0; + char linebuf[LINEBUF_SIZE]; + char *s; + int ret; + + if (! get_line(options, linebuf, sizeof(linebuf), stream)) + return 0; + + if (! options->allow_setenv) + { + if (options->warnings) + log(LOG_WARNING, "Attempted to set/unset an environment variable without permission"); + + return 0; /* Access Denied */ + } + + s = strchr(linebuf, '='); + + if (s != NULL && strncmp("set ", linebuf, (size_t)4) == 0) + { + /* Set an environment variable */ + + s = linebuf + 4; + + if (options->debug) + log(LOG_DEBUG, "setenv: %s", s); + + ret = pam_putenv(pamh, s); + if (ret != PAM_SUCCESS) + { + log(LOG_ERR, "Error setting \"%s\": %s", s, pam_strerror(pamh, ret)); + status = 1; + } + } + else if (s == NULL && strncmp("unset ", linebuf, (size_t)6) == 0) + { + /* Unset an environment variable */ + + s = linebuf + 6; + + if (options->debug) + log(LOG_DEBUG, "unsetenv: %s", s); + + ret = pam_putenv(pamh, s); + +#ifdef PAM_BAD_ITEM + if (ret == PAM_BAD_ITEM) + { + if (options->debug) + log(LOG_DEBUG, "(\"%s\" was already unset)", s); + } + else +#endif + if (ret != PAM_SUCCESS) + { + log(LOG_ERR, "Error unsetting environment variable \"%s\": %s", s, pam_strerror(pamh, ret)); + status = 1; + } + } + else + { + log(LOG_ERR, "Malformed environment variable directive"); + status = 1; + } + + return status; +} + +/* Reads the command's three channels of output, and dispatches + * accordingly. The three file descriptors passed in will be closed. + * Returns a PAM error code. + */ +static int +read_command_output(pam_handle_t *pamh, + struct exec_options *options, + int cmd_stdout_fd, + int cmd_stderr_fd, + int cmd_setenv_fd) +{ + FILE *cmd_stdout; + FILE *cmd_stderr; + FILE *cmd_setenv; + struct timeval timeout; + + if ((cmd_stdout = fdopen(cmd_stdout_fd, "r")) == NULL || + (cmd_stderr = fdopen(cmd_stderr_fd, "r")) == NULL || + (cmd_setenv = fdopen(cmd_setenv_fd, "r")) == NULL) + { + log(LOG_ERR, "fdopen() failed, errno = %d", errno); + return PAM_SYSTEM_ERR; + } + + for (;;) + { + fd_set fds_read; + int ret; + + FD_ZERO(&fds_read); + FD_SET(cmd_stdout_fd, &fds_read); + FD_SET(cmd_stderr_fd, &fds_read); + FD_SET(cmd_setenv_fd, &fds_read); + + /* The Linux implementation of select() needs this to be + * reinitialized at each iteration + */ + timeout.tv_sec = 0; + timeout.tv_usec = 10000; /* 1/100 second */ + + ret = select(cmd_setenv_fd + 1, &fds_read, NULL, NULL, &timeout); + if (ret == 0) + continue; + + if (ret < 0) + { + log(LOG_ERR, "select() failed, errno = %d", errno); + break; + } + + ret = 0; + + if (FD_ISSET(cmd_stdout_fd, &fds_read)) + ret |= read_command_stdout(pamh, options, cmd_stdout); + + if (FD_ISSET(cmd_stderr_fd, &fds_read)) + ret |= read_command_stderr(pamh, options, cmd_stderr); + + if (FD_ISSET(cmd_setenv_fd, &fds_read)) + ret |= read_command_setenv(pamh, options, cmd_setenv); + + /* Watch as we completely ignore ret! */ + + if (feof(cmd_stdout) && + feof(cmd_stderr) && + feof(cmd_setenv)) + { + break; + } + } + + if (fclose(cmd_stdout) || + fclose(cmd_stderr) || + fclose(cmd_setenv)) + { + log(LOG_ERR, "fclose() failed, errno = %d", errno); + return PAM_SYSTEM_ERR; + } + + return PAM_SUCCESS; } +/* Main pam_exec routine. Returns a PAM error code. + */ +static int +do_exec(pam_handle_t *pamh, + struct exec_options *options, + PamExecAction action) +{ + int status = PAM_SUCCESS; + sigset_t sigmask; + sigset_t old_sigmask; + int sigchld_blocked = 0; + const char *login_user; + const char *login_service; + const char *login_tty; + const char *login_ruser; + const char *login_rhost; + const char *login_authtok; + const char *login_oldauthtok; + struct passwd login_user_pw, *login_user_pw_ptr; + size_t pw_buflen; + char *pw_buf = NULL; + int opened_stdin = 0; + int opened_stdout = 0; + int opened_stderr = 0; + int opened_setenv = 0; + int stdout_pipe[2]; + int stderr_pipe[2]; + int setenv_pipe[2]; + pid_t pid; + pid_t ret_pid; + int cmd_status; + int ret; + + /* This function has a postamble that must not be bypassed. We + * temporarily disable the "return" keyword to ensure this. (The + * forked child section will use _exit(), so we're good there.) + */ +#define return dont_return_use_goto_cleanup + + /* Temporarily block SIGCHLD, so that the application program can't + * reap our child when the latter finishes (needed for gdm et al.) + */ + if (sigemptyset(&sigmask) || + sigaddset(&sigmask, SIGCHLD) || + sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask)) + { + log(LOG_ERR, "Could not block SIGCHLD, errno = %d", errno); + goto cleanup; + } + else + sigchld_blocked = 1; + + /* All right, who is this all about? + */ + ret = pam_get_user(pamh, (const char **)&login_user, NULL); + if (ret != PAM_SUCCESS) + { +#if defined(PAM_CONV_AGAIN) && defined(PAM_INCOMPLETE) + + if (ret == PAM_CONV_AGAIN) + { + status = PAM_INCOMPLETE; + goto cleanup; + } +#endif + + log(LOG_ERR, "Cannot get login name: %s", pam_strerror(pamh, ret)); + status = ret; + goto cleanup; + } + + if (options->debug) + log(LOG_DEBUG, "pam_get_user() => \"%s\"", login_user); + + /* * * + * Get some informative PAM items + */ + +#define GET_PAM_ITEM(arg_item, arg_var, arg_errmsg) \ + ret = pam_get_item(pamh, arg_item, (const void **)&arg_var); \ + if (ret != PAM_SUCCESS) \ + { \ + log(LOG_ERR, arg_errmsg); \ + status = PAM_SERVICE_ERR; \ + goto cleanup; \ + } \ + if (arg_var == NULL) \ + arg_var = ""; \ + if (options->debug && \ + arg_item != PAM_AUTHTOK && \ + arg_item != PAM_OLDAUTHTOK) \ + log(LOG_DEBUG, "pam_get_item(" #arg_item ") => \"%s\"", arg_var) + + GET_PAM_ITEM(PAM_SERVICE, login_service, "Cannot obtain service name"); + GET_PAM_ITEM(PAM_TTY, login_tty, "Cannot determine login tty"); + GET_PAM_ITEM(PAM_RUSER, login_ruser, "Cannot determine remote user"); + GET_PAM_ITEM(PAM_RHOST, login_rhost, "Cannot determine remote host"); + + if (options->allow_authtok) + { + GET_PAM_ITEM(PAM_AUTHTOK, + login_authtok, + "Cannot obtain authentication token"); + + GET_PAM_ITEM(PAM_OLDAUTHTOK, + login_oldauthtok, + "Cannot obtain old authentication token"); + } + +#undef GET_PAM_ITEM + + /* * * + * Get passwd info for the user logging in + */ + + pw_buflen = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX); + pw_buf = (char *)malloc(pw_buflen); + if (pw_buf == NULL) + { + status = PAM_BUF_ERR; + goto cleanup; + } + + ret = getpwnam_r(login_user, + &login_user_pw, + pw_buf, + pw_buflen, + &login_user_pw_ptr); + + if (ret != 0 || login_user_pw_ptr != &login_user_pw) + { + log(LOG_ERR, "Cannot obtain passwd info for user \"%s\"", login_user); + status = PAM_SYSTEM_ERR; + goto cleanup; + } + + /* * * + * Set up file descriptors + */ + + /* We don't want the pipe() calls below to use any of our four + * reserved file descriptors. These may or may not be currently + * open, depending on what the application program has done, so we + * temporarily open whichever ones aren't (thereby keeping them + * unavailable to pipe()). */ + +#define OPEN_IF_CLOSED(arg_fd, arg_flag) \ + if (! is_open_fd(arg_fd)) \ + { \ + int null_fd = open("/dev/null", O_RDONLY); \ + if (null_fd != arg_fd) \ + dup2(null_fd, arg_fd); \ + arg_flag = 1; \ + if (options->debug) \ + log(LOG_DEBUG, "fd %d was closed, now open (%d too)", arg_fd, null_fd); \ + } \ + (void)0 + + OPEN_IF_CLOSED(STDIN_FILENO, opened_stdin); + OPEN_IF_CLOSED(STDOUT_FILENO, opened_stdout); + OPEN_IF_CLOSED(STDERR_FILENO, opened_stderr); + OPEN_IF_CLOSED(SETENV_FILENO, opened_setenv); + +#undef OPEN_IF_CLOSED + + if (pipe(stdout_pipe) || + pipe(stderr_pipe) || + pipe(setenv_pipe)) + { + log(LOG_ERR, "pipe() failed, errno = %d", errno); + status = PAM_SYSTEM_ERR; + goto cleanup; + } + + if ((opened_stdin && close(STDIN_FILENO)) || + (opened_stdout && close(STDOUT_FILENO)) || + (opened_stderr && close(STDERR_FILENO)) || + (opened_setenv && close(SETENV_FILENO))) + { + log(LOG_ERR, "close() failed! errno = %d", errno); + status = PAM_SYSTEM_ERR; + goto cleanup; + } + + pid = fork(); + if (pid < 0) + { + log(LOG_ERR, "fork() failed, errno = %d", errno); + status = PAM_SYSTEM_ERR; + goto cleanup; + } + else if (pid == 0) + { + /**** Child ****/ + + char **envlist; + char **env; + const char *action_name = ""; + int fd; + + /* Take on our new identity + */ + if ((options->runas_gid != (gid_t)-1 && + setregid(options->runas_gid, + options->runas_gid)) + || + (options->runas_uid != (uid_t)-1 && + setreuid(options->runas_uid, + options->runas_uid))) + { + log(LOG_ERR, "Error changing uid/gid, errno = %d", errno); + _exit(PAM_SYSTEM_ERR); + } + + /* * * + * Set up those environment variables we promised + */ + + /* Current PAM environment */ + + envlist = pam_getenvlist(pamh); + + for (env = envlist; *env != NULL; env = &env[1]) + { + if (putenv(*env)) + { + log(LOG_ERR, "putenv() failed, errno = %d", errno); + _exit(PAM_BUF_ERR); + } + } + + /* Free the array of pointers to [environment-variable + * assignment] strings, but not the strings themselves, + * as they have become one with the environment + */ + free(envlist); + + /* Standard PAM items */ + + if (setenv("PAM_USER", login_user, 1) || + setenv("PAM_SERVICE", login_service, 1) || + setenv("PAM_TTY", login_tty, 1) || + setenv("PAM_RHOST", login_rhost, 1) || + setenv("PAM_RUSER", login_ruser, 1) || + (options->allow_authtok && + (setenv("PAM_AUTHTOK", login_authtok, 1) || + setenv("PAM_OLDAUTHTOK", login_oldauthtok, 1)))) + { + log(LOG_ERR, "setenv() failed, errno = %d", errno); + _exit(PAM_SYSTEM_ERR); + } + + /* Standard PAM flags */ + +#define SETENV_FLAG(arg_flag) \ + if (options->flags & arg_flag) \ + { \ + if (setenv(#arg_flag, "true", 1) != 0) \ + { \ + log(LOG_ERR, "setenv() failed, errno = %d", errno); \ + _exit(PAM_SYSTEM_ERR); \ + } \ + } \ + (void)0 + + SETENV_FLAG(PAM_SILENT); + SETENV_FLAG(PAM_DISALLOW_NULL_AUTHTOK); + SETENV_FLAG(PAM_CHANGE_EXPIRED_AUTHTOK); + SETENV_FLAG(PAM_PRELIM_CHECK); + SETENV_FLAG(PAM_UPDATE_AUTHTOK); + +#undef SET_FLAG_VAR + + /* Password database information */ + + setenv("PW_NAME", login_user_pw.pw_name, 1); + setenv("PW_GECOS", login_user_pw.pw_gecos, 1); + setenv("PW_DIR", login_user_pw.pw_dir, 1); + setenv("PW_SHELL", login_user_pw.pw_shell, 1); + + { + char idstr[16]; + + sprintf(idstr, "%d", (int)login_user_pw.pw_uid); + setenv("PW_UID", idstr, 1); + + sprintf(idstr, "%d", (int)login_user_pw.pw_gid); + setenv("PW_GID", idstr, 1); + } + + /* PAM-related symbolic exit codes */ + +#define SETENV_EXIT_CODE(arg_code) \ + { \ + char buf[8]; \ + sprintf(buf, "%d", arg_code); \ + if (setenv("EXIT_" #arg_code, buf, 1) != 0) \ + { \ + log(LOG_ERR, "setenv() failed, errno = %d", errno); \ + _exit(PAM_SYSTEM_ERR); \ + } \ + } \ + (void)0 + + SETENV_EXIT_CODE(PAM_ACCT_EXPIRED); + SETENV_EXIT_CODE(PAM_AUTHINFO_UNAVAIL); + SETENV_EXIT_CODE(PAM_AUTH_ERR); + SETENV_EXIT_CODE(PAM_CRED_INSUFFICIENT); + SETENV_EXIT_CODE(PAM_IGNORE); + SETENV_EXIT_CODE(PAM_MAXTRIES); + SETENV_EXIT_CODE(PAM_NEW_AUTHTOK_REQD); + SETENV_EXIT_CODE(PAM_PERM_DENIED); + SETENV_EXIT_CODE(PAM_SERVICE_ERR); + SETENV_EXIT_CODE(PAM_SESSION_ERR); + SETENV_EXIT_CODE(PAM_SUCCESS); + SETENV_EXIT_CODE(PAM_USER_UNKNOWN); + +#undef SETENV_EXIT_CODE + + /* * * + * Do the necessary POSIX-fu to get IPC working + */ + + /* Close the read ends of these pipes + */ + if (close(stdout_pipe[0]) || + close(stderr_pipe[0]) || + close(setenv_pipe[0])) + { + log(LOG_ERR, "close() failed! errno = %d", errno); + _exit(PAM_SYSTEM_ERR); + } + + /* Close the existing standard filehandles, as we will be + * substituting our own. These may or may not be open right + * now, so we don't bother checking the return values. + */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + close(SETENV_FILENO); + + if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1 || + dup2(stderr_pipe[1], STDERR_FILENO) == -1 || + dup2(setenv_pipe[1], SETENV_FILENO) == -1) + { + log(LOG_ERR, "dup2() failed, errno = %d", errno); + _exit(PAM_SYSTEM_ERR); + } + + /* command = 0; + fd--) + { + if (fd != stdout_pipe[1] && + fd != stderr_pipe[1] && + fd != setenv_pipe[1] && + fd != STDIN_FILENO && + fd != STDOUT_FILENO && + fd != STDERR_FILENO && + fd != SETENV_FILENO) + { + close(fd); + } + } + + /* * * + * ...aaaand, action! + */ + +#define ACTION_CASE(arg_action, arg_name) \ + case arg_action: action_name = arg_name; break + + switch (action) + { + ACTION_CASE(PE_AUTHENTICATE, "auth"); + /* ACTION_CASE(PE_SETCRED, "setcred"); */ + ACTION_CASE(PE_ACCT_MGMT, "acctmgmt"); + ACTION_CASE(PE_OPEN_SESSION, "sessopen"); + ACTION_CASE(PE_CLOSE_SESSION, "sessclose"); + ACTION_CASE(PE_CHAUTHTOK, "chauthtok"); + } + +#undef ACTION_CASE + + setenv("ACTION", action_name, 1); + + if (options->debug) + log(LOG_DEBUG, "exec: /bin/sh -c '%s' /bin/sh %s", options->command, action_name); + + execl("/bin/sh", + "/bin/sh", + "-c", + options->command, + "/bin/sh", + action_name, + (char *)NULL); /* the cast is required */ + + /* If we can't exec /bin/sh then the system is FUBAR */ + + log(LOG_ERR, "execl() failed, errno = %d", errno); + _exit(PAM_SYSTEM_ERR); + } + + /**** Parent ****/ + + /* Close the write ends of these pipes + */ + if (close(stdout_pipe[1]) || + close(stderr_pipe[1]) || + close(setenv_pipe[1])) + { + log(LOG_ERR, "close() failed! errno = %d", errno); + status = PAM_SYSTEM_ERR; + goto cleanup; + } + + /* * * * + * * * + * * + */ + + ret = read_command_output(pamh, + options, + stdout_pipe[0], + stderr_pipe[0], + setenv_pipe[0]); + /* + * * + * * * + * * * */ + + do { + ret_pid = waitpid(pid, &cmd_status, 0); + } + while (ret_pid == (pid_t)-1 && errno == EINTR); + + if (ret_pid == -1 || ret_pid != pid) + { + log(LOG_ERR, "waitpid() failed, errno = %d", errno); + status = PAM_SYSTEM_ERR; + goto cleanup; + } + + if (WIFEXITED(cmd_status)) + { + status = WEXITSTATUS(cmd_status); + + if (options->debug) + log(LOG_DEBUG, "Command exited with status %d", status); + } + else if (WIFSIGNALED(cmd_status)) + { + int ts = WTERMSIG(cmd_status); + const char *cd = ""; + +#ifdef WCOREDUMP + if (WCOREDUMP(cmd_status)) + cd = " (core dumped)"; +#endif + + if (options->warnings) + log(LOG_WARNING, "Command terminated on signal %d%s", ts, cd); + + status = PAM_SERVICE_ERR; + } + else + { + log(LOG_ERR, "Command exited with unknown status 0x%x", cmd_status); + status = PAM_SERVICE_ERR; + } + + /* * * + * Clean up after ourselves + */ + + cleanup: + + if (sigchld_blocked) + { + /* Restore previous signal mask + */ + sigprocmask(SIG_SETMASK, &old_sigmask, NULL); + } + + if (pw_buf != NULL) + free(pw_buf); + + /* Re-enable the "return" keyword + */ +#undef return + + return status; + +} /* end do_exec() */ + +/* Save ourselves some typing + */ +#define COMMON_ERROR_CODES() \ + case PAM_SUCCESS: \ + case PAM_SERVICE_ERR: \ + case PAM_SYSTEM_ERR: \ + case PAM_BUF_ERR: \ + case PAM_CONV_ERR: \ + case PAM_IGNORE: \ + (void)0 + +/* * * + * Authentication management + */ + PAM_EXTERN int -pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED, - int argc, const char **argv) +pam_sm_authenticate(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { - return call_exec (pamh, argc, argv); + struct exec_options options; + int ret; + + ret = parse_options(&options, flags, argc, argv, PE_AUTHENTICATE); + if (ret != PAM_SUCCESS) + return ret; + +#ifdef PAM_FAIL_DELAY + if (options.fail_delay > 0) + pam_fail_delay(pamh, (unsigned int)options.fail_delay * 1000); +#endif + + ret = do_exec(pamh, &options, PE_AUTHENTICATE); + switch (ret) + { + COMMON_ERROR_CODES(); + case PAM_AUTH_ERR: + case PAM_CRED_INSUFFICIENT: + case PAM_AUTHINFO_UNAVAIL: + case PAM_USER_UNKNOWN: + case PAM_MAXTRIES: + return ret; + } + + return PAM_AUTH_ERR; } PAM_EXTERN int -pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED, - int argc UNUSED, const char **argv UNUSED) +pam_sm_setcred(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { - return PAM_IGNORE; + pamh = pamh; /* unused */ + flags = flags; /* unused */ + argc = argc; /* unused */ + argv = argv; /* unused */ + + /* This module task is not well-served by an external program + */ + return PAM_IGNORE; } -/* password updating functions */ +/* * * + * Account management + */ PAM_EXTERN int -pam_sm_chauthtok(pam_handle_t *pamh, int flags, - int argc, const char **argv) +pam_sm_acct_mgmt(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { - if (flags & PAM_PRELIM_CHECK) - return PAM_SUCCESS; - return call_exec (pamh, argc, argv); + struct exec_options options; + int ret; + + ret = parse_options(&options, flags, argc, argv, PE_ACCT_MGMT); + if (ret != PAM_SUCCESS) + return ret; + + ret = do_exec(pamh, &options, PE_ACCT_MGMT); + switch (ret) + { + COMMON_ERROR_CODES(); + case PAM_ACCT_EXPIRED: + case PAM_AUTH_ERR: + /* case PAM_AUTHTOKEN_REQD: */ + case PAM_USER_UNKNOWN: + case PAM_NEW_AUTHTOK_REQD: /* from Sun PAM */ + case PAM_PERM_DENIED: /* from Sun PAM */ + return ret; + } + + return PAM_AUTH_ERR; } +/* * * + * Session management + */ + PAM_EXTERN int -pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED, - int argc, const char **argv) +pam_sm_open_session(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { - return call_exec (pamh, argc, argv); + struct exec_options options; + int ret; + + ret = parse_options(&options, flags, argc, argv, PE_OPEN_SESSION); + if (ret != PAM_SUCCESS) + return ret; + + if (! options.on_open) + return PAM_IGNORE; + + ret = do_exec(pamh, &options, PE_OPEN_SESSION); + switch (ret) + { + COMMON_ERROR_CODES(); + case PAM_SESSION_ERR: + return ret; + } + + return PAM_SESSION_ERR; } PAM_EXTERN int -pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, - int argc, const char **argv) +pam_sm_close_session(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { - return call_exec (pamh, argc, argv); + struct exec_options options; + int ret; + + ret = parse_options(&options, flags, argc, argv, PE_CLOSE_SESSION); + if (ret != PAM_SUCCESS) + return ret; + + if (! options.on_close) + return PAM_IGNORE; + + ret = do_exec(pamh, &options, PE_CLOSE_SESSION); + switch (ret) + { + COMMON_ERROR_CODES(); + case PAM_SESSION_ERR: + return ret; + } + + return PAM_SESSION_ERR; } +/* * * + * Password management + */ + PAM_EXTERN int -pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED, - int argc, const char **argv) +pam_sm_chauthtok(pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { - return call_exec (pamh, argc, argv); + struct exec_options options; + int ret; + + ret = parse_options(&options, flags, argc, argv, PE_CHAUTHTOK); + if (ret != PAM_SUCCESS) + return ret; + + ret = do_exec(pamh, &options, PE_CHAUTHTOK); + switch (ret) + { + COMMON_ERROR_CODES(); + case PAM_AUTHTOK_ERR: + case PAM_AUTHTOK_RECOVERY_ERR: + case PAM_AUTHTOK_LOCK_BUSY: + case PAM_AUTHTOK_DISABLE_AGING: + case PAM_PERM_DENIED: + case PAM_TRY_AGAIN: + case PAM_USER_UNKNOWN: + return ret; + } + + return PAM_AUTHTOK_ERR; } +#undef COMMON_ERROR_CODES + #ifdef PAM_STATIC -struct pam_module _pam_exec_modstruct = { - "pam_exec", - pam_sm_authenticate, - pam_sm_setcred, - pam_sm_acct_mgmt, - pam_sm_open_session, - pam_sm_close_session, - pam_sm_chauthtok, + +struct pam_module _pam_exec_modstruct = +{ + "pam_exec", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok, }; -#endif + +#endif /* PAM_STATIC */ + +/* end pam_exec.c */ Index: modules/pam_exec/pam_exec.8.xml =================================================================== RCS file: /cvsroot/pam/Linux-PAM/modules/pam_exec/pam_exec.8.xml,v retrieving revision 1.4 diff -u -r1.4 pam_exec.8.xml --- modules/pam_exec/pam_exec.8.xml 9 Jun 2006 16:44:06 -0000 1.4 +++ modules/pam_exec/pam_exec.8.xml 30 Jul 2006 08:58:34 -0000 @@ -1,4 +1,4 @@ - + @@ -12,39 +12,113 @@ pam_exec - PAM module which calls an external command + PAM module to invoke an external command - - pam_exec.so + + + account ctrlflag pam_exec.so + + debug + + + no_warn + + + runas=userspec + + + setenv + + + exec command_string + + + + + auth ctrlflag pam_exec.so + + authtok + + + debug + + + fail_delay=delay + + + no_warn + + + runas=userspec + + + setenv + + + exec command_string + + + + + password ctrlflag pam_exec.so + + authtok + debug - seteuid + fail_delay=delay - log=file + no_warn + + + runas=userspec + + + setenv - command + exec command_string + + + + + session ctrlflag pam_exec.so + + debug + + + no_warn + + + on_open_only - ... + on_close_only + + + runas=userspec + + + setenv + + + exec command_string + - DESCRIPTION - - pam_exec is a PAM module that can be used to run - an external command. + The pam_exec PAM module invokes an external + script or other arbitrary shell command as part of a PAM stack. - @@ -55,64 +129,598 @@ + + + + + Allow the command access to authentication tokens (i.e. + passwords) via the PAM_AUTHTOK and PAM_OLDAUTHTOK environment + variables. This is applicable only to the auth and passwd + module services. + + + + + + - Print debug information. + Write superfluous LOG_DEBUG messages to syslog, to assist in + debugging the module or its invocation. + + + + + + + + + + + A minimum time delay, in milliseconds, that PAM should wait + if authentication fails via pam_exec. + + + + + + + + + + + Don't write LOG_WARNING messages to syslog. The use of this + option is not recommended. + + + + + + + + + + + Invoke the command only when a session is opened. (The + default is to invoke it both at the beginning and end of a + session.) This is meaningful only when + pam_exec is called via the session + facility. - + - The output of the command is appended to - file + Invoke the command only when a session is closed. (See the + description of above.) + + + + + + + + + + + Invoke the command as a different user and/or group. userspec + is a string of the form user, + user:group + or :group, as one might specify to + chown(1). Both the user and group can be specified by name, + or by their numeric ID. + + + The default is to invoke the command with the same user and + group identity (usually root) as the PAM-enabled application. + Note that if the application is not running as root, + pam_exec may not have sufficient + privilege to change the command's invocation identity. - + - Per default pam_exec.so will execute the external command - with the real user ID of the calling process. - Specifying this option means the command is run - with the effective user ID. + Allow the command to modify the PAM session environment via a + file descriptor. See below. + + + + + + + + + + + The command to invoke. This will be passed to /bin/sh -c, so + shell redirections and variable substitutions may be used. + + + + COMMAND EXECUTION ENVIRONMENT + + The command cannot accept direct input from the user, due to the + constraints of PAM and potential security implications. Under certain + circumstances, however, it can be allowed access to the user's + authentication token. See the option for + details. + + + + The command's standard output is fed to the PAM conversation + mechanism. Provided that the PAM-enabled application did not set the + PAM_SILENT flag, any output to this stream should be visible to the + user. + + + The command's standard error will be logged as LOG_INFO messages in + syslog. + + + If the option is given, the command will be + invoked with an additional file descriptor open for writing; + specially formatted directives for setting/unsetting variables in the + PAM environment (which will be inherited by any forthcoming user + session) may be written to this file descriptor. See + below for a detailed discussion of + this interface. + + + Several environment variables are made available to the command. + These include informative variables provided by + pam_exec, variables in the current PAM + environment (which may include e.g. KRB5CCNAME if Kerberos + authentication is in use), and those in the environment of the + PAM-enabled application. Below is a (hopefully complete) list of + variables that may be expected. + + + + Basic informational items: + + + ACTION + + + The PAM action currently being performed. This will be set to + one of auth, acctmgmt, + sessopen, sessclose + or chauthtok. It is also accessible as + $1 (positional parameter 1) within the command string itself. + + + + + + PAM_USER + + + The username of the user for which PAM is being invoked. + + + + + + PAM_SERVICE + + + The name of the service for which PAM is being invoked (e.g. + login, sshd, + passwd). + + + + + + PAM_TTY + + + The terminal device on which PAM is being invoked. This + variable may not always be set to a sensible value. + + + + + + PAM_RHOST + + + The remote host, in the context of PAM authentication. + + + + + + PAM_RUSER + + + The remote user, in the context of PAM authentication. + + + + + + PAM_AUTHTOK + + + If the option was given, then this + variable will be set to the user's authentication token + (a.k.a. password). Beware! This piece of information is + extremely sensitive, and in certain runtime environments + (e.g. shell scripts), can be inadvertently leaked. + Do not use this variable unless you know + what you are doing. You have been warned. + + + + + + PAM_OLDAUTHTOK + + + The user's previous authentication token, in the context of a + password-change operation. + + + + + + + + + PAM flags set by the application: + + + PAM_SILENT + + + If set, then the PAM-enabled application has requested silent + mode. No output written to stdout will be relayed to the + user, but ideally, the program invoked by + pam_exec will refrain from attempting to + do so in the presence of this flag. + + + + + + PAM_DISALLOW_NULL_AUTHTOK + + + If set, then null authentication tokens (i.e. empty + passwords) should not be permitted. + + + + + + PAM_CHANGE_EXPIRED_AUTHTOK + + + If set, ???????? + + + + + + PAM_PRELIM_CHECK + + + If set, ???????? + + + + + + PAM_UPDATE_AUTHTOK + + + If set, ???????? + + + + + + + + + Information about the user: + + + PW_NAME + + + Generally a synonym for PAM_USER. This variable stores the + name of the user as present in the system password database. + + + + + + PW_GECOS + + + The GECOS field from the password database record for the + user named by PW_NAME. + + + + + + PW_DIR + + + The home directory for $PW_NAME, per the password database. + + + + + + PW_SHELL + + + The default command interpreter (i.e. shell) for $PW_NAME, + per the password database. + + + + + + PW_UID + + + The numeric user ID of $PW_NAME, per the password database. + + + + + + PW_GID + + + The numeric primary group ID of $PW_NAME, per the password + database. + + + + + + + + The following variables may be used as symbolic exit codes, to signal + errors meaningfully to PAM. For example, a shell script which fails + to recognize the user may perform + + exit $EXIT_PAM_USER_UNKNOWN + + instead of exiting with a nondescript status of, say, 1. + + + Note that while each of these variables represents a distinct integer + value, one should not use the integers directly as they are not + standardized across the different implementations of PAM. The only + exception is (EXIT_)PAM_SUCCESS, which is always zero. + + + + Symbolic exit codes: + + + EXIT_PAM_SUCCESS + + + Signals a successful authentication, or otherwise an + operation that completed without error. + + + + + + EXIT_PAM_IGNORE + + + Instructs PAM to ignore this invocation of + pam_exec. + + + + + + EXIT_PAM_ACCT_EXPIRED + + + ???????? + + + + + + EXIT_PAM_AUTHINFO_UNAVAIL + + + ???????? + + + + + + EXIT_PAM_AUTH_ERR + + + An authentication error occurred. + + + + + + EXIT_PAM_CRED_INSUFFICIENT + + + ???????? + + + + + + EXIT_PAM_MAXTRIES + + + The maximum number of permissible attempts (at + authentication, password changes, etc.) has been exceeded. + + + + + + EXIT_PAM_NEW_AUTHTOK_REQD + + + ???????? + + + + + + EXIT_PAM_PERM_DENIED + + + Permission is denied for the requested operation. + + + + + + EXIT_PAM_SERVICE_ERR + + + An error occurred in the underlying logic of + pam_exec or the program it has invoked. + + + + + + EXIT_PAM_SESSION_ERR + + + ???????? + + + + + + EXIT_PAM_USER_UNKNOWN + + + ???????? + + + + + + + + + + MODIFYING THE PAM ENVIRONMENT + + If the option is passed to + pam_exec, the command is allowed to modify the + PAM environment by writing directives to file descriptor 3. The + following directives are permitted: + + + + + + + + set NAME=value of variable + + + + The environment variable of the given NAME is set to the + value value of variable. Any + previous value held by NAME is lost. + + + + + + + set NAME= + + + + The environment variable NAME is set to the empty value. This + is listed separately to affirm that it is not a special + case. + + + + + + + unset NAME + + + + Unsets the environment variable NAME. + + + + + MODULE SERVICES PROVIDED - The services , , - and are supported. + Pam_exec supports the , , + and services. - + RETURN VALUES + The return value, in most cases, should be that returned by the + command. + + PAM_SUCCESS - The external command runs successfull. + The command exited successfully. @@ -121,7 +729,8 @@ PAM_SERVICE_ERR - No argument or a wrong number of arguments were given. + There was an error in the invocation of + pam_exec. @@ -130,7 +739,7 @@ PAM_SYSTEM_ERR - A system error occured or the command to execute failed. + A system error occurred in attempting to execute the command. @@ -139,8 +748,8 @@ PAM_IGNORE - pam_setcred was called, which - does not execute the command. + This value may be returned if the application calls the pam_setcred function, which is not + served by pam_exec. @@ -149,22 +758,31 @@ - + EXAMPLES + Add the following line to /etc/pam.d/passwd to rebuild the NIS database after each local password change: - passwd optional pam_exec.so seteuid make -C /var/yp + passwd optional pam_exec.so exec make -C /var/yp + - This will execute the command - make -C /var/yp - with effective user ID. + + Make a noise when someone logs in via the SSH service, with the + frequency based on the user ID. In + /etc/pam.d/ssh: + + session optional pam_exec.so on_open_only exec beep -f $PW_UID -l 500 + + (This makes use of Johnathan Nightingale's beep(1) utility, available + from http://www.johnath.com/beep/) + - + SEE ALSO @@ -179,11 +797,13 @@ - + AUTHOR - - pam_exec was written by Thorsten Kukuk <kukuk@thkukuk.de>. - + + The Linux-PAM implementation of pam_exec was + first written by Thorsten Kukuk <kukuk@thkukuk.de>, and later + elaborated by Daniel Richard G. <skunk@iSKUNK.ORG>. +