Package: libpam-modules Version: 1.1.3-7 Severity: wishlist Tags: patch The attached patches implement two new options for the pam_exec module.
Patch 1 adds a "stdout" option, which shows the stdout (and stderr) of the executed command via pam_info. For instance, adding the following line to /etc/pam.d/login right before the line for pam_motd: session optional pam_exec.so stdout /usr/bin/seq 5 will print five lines (numbered 1-5) at the start and end of the session. In order to implement this option without breaking the existing support for the expose_authtok option, I had to reorganize the file descriptor handling to move the loop that closes all unwanted file descriptors below all the code that sets up stdin/stdout/stderr, and add some new code before that setup to ensure that none of the pipes ended up on stdin/stdout/stderr where they might get closed by dup2. Patch 2 adds a "type" option, which causes pam_exec to only execute the command when the PAM module type matches the given type. In particular, this makes it possible to run only at the start or end of a session, without having to write a separate wrapper script to check the PAM_TYPE environment variable. For example, adding the following to /etc/pam.d/login right before the line for pam_motd: session optional pam_exec.so type=open_session /bin/sleep 5 will sleep for 5 seconds at login time, but not at logout time, demonstrating that the option works. Together, these options make it possible to show dynamically generated output at the start of a PAM session. For example, the following pam_exec invocation has the same effect as the current dynamically-generated first line of the motd: session optional pam_exec.so type=open_session stdout /bin/uname -snrvm CCing Roger Leigh, who plans to use this functionality to replace the current dynamically generated motd. - Josh Triplett
=== modified file 'modules/pam_exec/pam_exec.8.xml' --- modules/pam_exec/pam_exec.8.xml 2010-10-19 22:24:34 +0000 +++ modules/pam_exec/pam_exec.8.xml 2012-04-23 12:01:40 +0000 @@ -31,6 +31,9 @@ quiet </arg> <arg choice="opt"> + stdout + </arg> + <arg choice="opt"> log=<replaceable>file</replaceable> </arg> <arg choice="plain"> @@ -119,6 +122,17 @@ <varlistentry> <term> + <option>stdout</option> + </term> + <listitem> + <para> + Shows the output of the command. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> <option>quiet</option> </term> <listitem> @@ -236,7 +250,8 @@ <refsect1 id='pam_exec-author'> <title>AUTHOR</title> <para> - pam_exec was written by Thorsten Kukuk <ku...@thkukuk.de>. + pam_exec was written by Thorsten Kukuk <ku...@thkukuk.de> and + Josh Triplett <j...@joshtriplett.org>. </para> </refsect1> === modified file 'modules/pam_exec/pam_exec.c' --- modules/pam_exec/pam_exec.c 2009-04-03 07:36:22 +0000 +++ modules/pam_exec/pam_exec.c 2012-04-23 12:01:40 +0000 @@ -72,6 +72,24 @@ ENV_ITEM(PAM_RUSER), }; +/* move_fd_to_non_stdio copies the given file descriptor to something other + * than stdin, stdout, or stderr. Assumes that the caller will close all + * unwanted fds after calling. */ +static int +move_fd_to_non_stdio (pam_handle_t *pamh, int fd) +{ + while (fd < 3) + { + fd = dup(fd); + if (fd == -1) + { + int err = errno; + pam_syslog (pamh, LOG_ERR, "dup failed: %m"); + _exit (err); + } + } + return fd; +} static int call_exec (const char *pam_type, pam_handle_t *pamh, @@ -81,11 +99,14 @@ int call_setuid = 0; int quiet = 0; int expose_authtok = 0; + int use_stdout = 0; int optargc; const char *logfile = NULL; const char *authtok = NULL; pid_t pid; int fds[2]; + int stdout_fds[2]; + FILE *stdout_file = NULL; if (argc < 1) { pam_syslog (pamh, LOG_ERR, @@ -100,6 +121,8 @@ if (strcasecmp (argv[optargc], "debug") == 0) debug = 1; + else if (strcasecmp (argv[optargc], "stdout") == 0) + use_stdout = 1; else if (strncasecmp (argv[optargc], "log=", 4) == 0) logfile = &argv[optargc][4]; else if (strcasecmp (argv[optargc], "seteuid") == 0) @@ -164,6 +187,21 @@ } } + if (use_stdout) + { + if (pipe(stdout_fds) != 0) + { + pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m"); + return PAM_SYSTEM_ERR; + } + stdout_file = fdopen(stdout_fds[0], "r"); + if (!stdout_file) + { + pam_syslog (pamh, LOG_ERR, "Could not fdopen pipe: %m"); + return PAM_SYSTEM_ERR; + } + } + if (optargc >= argc) { pam_syslog (pamh, LOG_ERR, "No path given as argument"); return PAM_SERVICE_ERR; @@ -198,6 +236,21 @@ close(fds[1]); } + if (use_stdout) + { + char buf[4096]; + close(stdout_fds[1]); + while (fgets(buf, sizeof(buf), stdout_file) != NULL) + { + size_t len; + len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = '\0'; + pam_info(pamh, "%s", buf); + } + fclose(stdout_file); + } + while ((retval = waitpid (pid, &status, 0)) == -1 && errno == EINTR); if (retval == (pid_t)-1) @@ -245,6 +298,23 @@ int envlen, nitems; char *envstr; + /* First, move all the pipes off of stdin, stdout, and stderr, to ensure + * that calls to dup2 won't close them. */ + + if (expose_authtok) + { + fds[0] = move_fd_to_non_stdio(pamh, fds[0]); + close(fds[1]); + } + + if (use_stdout) + { + stdout_fds[1] = move_fd_to_non_stdio(pamh, stdout_fds[1]); + close(stdout_fds[0]); + } + + /* Set up stdin. */ + if (expose_authtok) { /* reopen stdin as pipe */ @@ -254,17 +324,10 @@ pam_syslog (pamh, LOG_ERR, "dup2 of STDIN failed: %m"); _exit (err); } - - for (i = 0; i < sysconf (_SC_OPEN_MAX); i++) - { - if (i != STDIN_FILENO) - close (i); - } } else { - for (i = 0; i < sysconf (_SC_OPEN_MAX); i++) - close (i); + close (STDIN_FILENO); /* New stdin. */ if ((i = open ("/dev/null", O_RDWR)) < 0) @@ -275,12 +338,23 @@ } } - /* New stdout and stderr. */ - if (logfile) + /* Set up stdout. */ + + if (use_stdout) + { + if (dup2(stdout_fds[1], STDOUT_FILENO) == -1) + { + int err = errno; + pam_syslog (pamh, LOG_ERR, "dup2 to stdout failed: %m"); + _exit (err); + } + } + else if (logfile) { time_t tm = time (NULL); char *buffer = NULL; + close (STDOUT_FILENO); if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { @@ -297,7 +371,7 @@ } else { - /* New stdout/stderr. */ + close (STDOUT_FILENO); if ((i = open ("/dev/null", O_RDWR)) < 0) { int err = errno; @@ -306,13 +380,16 @@ } } - if (dup (i) == -1) + if (dup2 (STDOUT_FILENO, STDERR_FILENO) == -1) { int err = errno; - pam_syslog (pamh, LOG_ERR, "dup failed: %m"); + pam_syslog (pamh, LOG_ERR, "dup2 failed: %m"); _exit (err); } + for (i = 3; i < sysconf (_SC_OPEN_MAX); i++) + close (i); + if (call_setuid) if (setuid (geteuid ()) == -1) {
=== modified file 'modules/pam_exec/pam_exec.8.xml' --- modules/pam_exec/pam_exec.8.xml 2012-04-23 12:01:40 +0000 +++ modules/pam_exec/pam_exec.8.xml 2012-04-23 12:33:04 +0000 @@ -36,6 +36,9 @@ <arg choice="opt"> log=<replaceable>file</replaceable> </arg> + <arg choice="opt"> + type=<replaceable>type</replaceable> + </arg> <arg choice="plain"> <replaceable>command</replaceable> </arg> @@ -122,6 +125,17 @@ <varlistentry> <term> + <option>type=<replaceable>type</replaceable></option> + </term> + <listitem> + <para> + Only run the command if the module type matches the given type. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> <option>stdout</option> </term> <listitem> @@ -208,7 +222,8 @@ <listitem> <para> <function>pam_setcred</function> was called, which - does not execute the command. + does not execute the command. Or, the value given for the type= + parameter did not match the module type. </para> </listitem> </varlistentry> === modified file 'modules/pam_exec/pam_exec.c' --- modules/pam_exec/pam_exec.c 2012-04-23 12:01:40 +0000 +++ modules/pam_exec/pam_exec.c 2012-04-23 12:33:04 +0000 @@ -125,6 +125,11 @@ use_stdout = 1; else if (strncasecmp (argv[optargc], "log=", 4) == 0) logfile = &argv[optargc][4]; + else if (strncasecmp (argv[optargc], "type=", 5) == 0) + { + if (strcmp (pam_type, &argv[optargc][5]) != 0) + return PAM_IGNORE; + } else if (strcasecmp (argv[optargc], "seteuid") == 0) call_setuid = 1; else if (strcasecmp (argv[optargc], "quiet") == 0)