diff options
author | Thorsten Kukuk <kukuk@thkukuk.de> | 2004-09-23 15:41:58 +0000 |
---|---|---|
committer | Thorsten Kukuk <kukuk@thkukuk.de> | 2004-09-23 15:41:58 +0000 |
commit | 2ff0080fce12aa9593fddcb94f87db2973205468 (patch) | |
tree | 659f08f000e243caeaa2b31c2a46ef8a2730861a /modules/pam_xauth/pam_xauth.c | |
parent | c138e89215d2a93a6e2790c6e9065b05b5921837 (diff) | |
download | pam-2ff0080fce12aa9593fddcb94f87db2973205468.tar.gz pam-2ff0080fce12aa9593fddcb94f87db2973205468.tar.bz2 pam-2ff0080fce12aa9593fddcb94f87db2973205468.zip |
Relevant BUGIDs:
Purpose of commit:
Commit summary:
---------------
new feature: Add pam_xauth (Bug 436440)
Diffstat (limited to 'modules/pam_xauth/pam_xauth.c')
-rw-r--r-- | modules/pam_xauth/pam_xauth.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/modules/pam_xauth/pam_xauth.c b/modules/pam_xauth/pam_xauth.c new file mode 100644 index 00000000..58dd059a --- /dev/null +++ b/modules/pam_xauth/pam_xauth.c @@ -0,0 +1,621 @@ +/* + * Copyright 2001-2003 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. + */ + +#ident "$Id$" + +#include "../../_pam_aconf.h" +#include <sys/types.h> +#include <sys/fsuid.h> +#include <sys/wait.h> +#include <errno.h> +#include <fnmatch.h> +#include <grp.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <security/pam_modules.h> +#include <security/_pam_macros.h> +#include <security/_pam_modutil.h> + +#define DATANAME "pam_xauth_cookie_file" +#define XAUTHBIN "/usr/X11R6/bin/xauth" +#define XAUTHENV "XAUTHORITY" +#define HOMEENV "HOME" +#define XAUTHDEF ".Xauthority" +#define XAUTHTMP ".xauthXXXXXX" + +/* Run a given command (with a NULL-terminated argument list), feeding it the + * given input on stdin, and storing any output it generates. */ +static int +run_coprocess(const char *input, char **output, + uid_t uid, gid_t gid, const char *command, ...) +{ + int ipipe[2], opipe[2], i; + char buf[LINE_MAX]; + pid_t child; + char *buffer = NULL; + size_t buffer_size = 0; + va_list ap; + + *output = NULL; + + /* Create stdio pipery. */ + if (pipe(ipipe) == -1) { + return -1; + } + if (pipe(opipe) == -1) { + close(ipipe[0]); + close(ipipe[1]); + return -1; + } + + /* Fork off a child. */ + child = fork(); + if (child == -1) { + close(ipipe[0]); + close(ipipe[1]); + close(opipe[0]); + close(opipe[1]); + return -1; + } + + if (child == 0) { + /* We're the child. */ + char *args[10]; + const char *tmp; + /* Drop privileges. */ + setgid(gid); + setgroups(0, NULL); + setuid(uid); + /* Initialize the argument list. */ + memset(args, 0, sizeof(args)); + /* Set the pipe descriptors up as stdin and stdout, and close + * everything else, including the original values for the + * descriptors. */ + dup2(ipipe[0], STDIN_FILENO); + dup2(opipe[1], STDOUT_FILENO); + for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) { + if ((i != STDIN_FILENO) && (i != STDOUT_FILENO)) { + close(i); + } + } + /* Convert the varargs list into a regular array of strings. */ + va_start(ap, command); + args[0] = strdup(command); + for (i = 1; i < ((sizeof(args) / sizeof(args[0])) - 1); i++) { + tmp = va_arg(ap, const char*); + if (tmp == NULL) { + break; + } + args[i] = strdup(tmp); + } + /* Run the command. */ + execvp(command, args); + /* Never reached. */ + exit(1); + } + + /* We're the parent, so close the other ends of the pipes. */ + close(ipipe[0]); + close(opipe[1]); + /* Send input to the process (if we have any), then send an EOF. */ + if (input) { + (void)_pammodutil_write(ipipe[1], input, strlen(input)); + } + close(ipipe[1]); + + /* Read data output until we run out of stuff to read. */ + i = _pammodutil_read(opipe[0], buf, sizeof(buf)); + while ((i != 0) && (i != -1)) { + char *tmp; + /* Resize the buffer to hold the data. */ + tmp = realloc(buffer, buffer_size + i + 1); + if (tmp == NULL) { + /* Uh-oh, bail. */ + if (buffer != NULL) { + free(buffer); + } + close(opipe[0]); + waitpid(child, NULL, 0); + return -1; + } + /* Save the new buffer location, copy the newly-read data into + * the buffer, and make sure the result will be + * nul-terminated. */ + buffer = tmp; + memcpy(buffer + buffer_size, buf, i); + buffer[buffer_size + i] = '\0'; + buffer_size += i; + /* Try to read again. */ + i = _pammodutil_read(opipe[0], buf, sizeof(buf)); + } + /* No more data. Clean up and return data. */ + close(opipe[0]); + *output = buffer; + waitpid(child, NULL, 0); + return 0; +} + +/* Free a data item. */ +static void +cleanup(pam_handle_t *pamh, void *data, int err) +{ + free(data); +} + +/* Check if we want to allow export to the other user, or import from the + * other user. */ +static int +check_acl(pam_handle_t *pamh, + const char *sense, const char *this_user, const char *other_user, + int noent_code, int debug) +{ + char path[PATH_MAX]; + struct passwd *pwd; + FILE *fp; + int i; + uid_t euid; + /* Check this user's <sense> file. */ + pwd = _pammodutil_getpwnam(pamh, this_user); + if (pwd == NULL) { + syslog(LOG_ERR, "pam_xauth: error determining " + "home directory for '%s'", this_user); + return PAM_SESSION_ERR; + } + /* Figure out what that file is really named. */ + i = snprintf(path, sizeof(path), "%s/.xauth/%s", pwd->pw_dir, sense); + if ((i >= sizeof(path)) || (i < 0)) { + syslog(LOG_ERR, "pam_xauth: name of user's home directory " + "is too long"); + return PAM_SESSION_ERR; + } + euid = geteuid(); + setfsuid(pwd->pw_uid); + fp = fopen(path, "r"); + setfsuid(euid); + if (fp != NULL) { + char buf[LINE_MAX], *tmp; + /* Scan the file for a list of specs of users to "trust". */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + tmp = memchr(buf, '\r', sizeof(buf)); + if (tmp != NULL) { + *tmp = '\0'; + } + tmp = memchr(buf, '\n', sizeof(buf)); + if (tmp != NULL) { + *tmp = '\0'; + } + if (fnmatch(buf, other_user, 0) == 0) { + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: %s %s " + "allowed by %s", + other_user, sense, path); + } + fclose(fp); + return PAM_SUCCESS; + } + } + /* If there's no match in the file, we fail. */ + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: %s not listed in %s", + other_user, path); + } + fclose(fp); + return PAM_PERM_DENIED; + } else { + /* Default to okay if the file doesn't exist. */ + switch (errno) { + case ENOENT: + if (noent_code == PAM_SUCCESS) { + if (debug) { + syslog(LOG_DEBUG, "%s does not exist, " + "ignoring", path); + } + } else { + if (debug) { + syslog(LOG_DEBUG, "%s does not exist, " + "failing", path); + } + } + return noent_code; + default: + if (debug) { + syslog(LOG_ERR, "%s opening %s", + strerror(errno), path); + } + return PAM_PERM_DENIED; + } + } +} + +int +pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + char xauthpath[] = XAUTHBIN; + char *cookiefile = NULL, *xauthority = NULL, + *cookie = NULL, *display = NULL, *tmp = NULL; + const char *user, *xauth = xauthpath; + struct passwd *tpwd, *rpwd; + int fd, i, debug = 0; + uid_t systemuser = 499, targetuser = 0, euid; + + /* Parse arguments. We don't understand many, so no sense in breaking + * this into a separate function. */ + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug = 1; + continue; + } + if (strncmp(argv[i], "xauthpath=", 10) == 0) { + xauth = argv[i] + 10; + continue; + } + if (strncmp(argv[i], "targetuser=", 11) == 0) { + long l = strtol(argv[i] + 11, &tmp, 10); + if ((strlen(argv[i] + 11) > 0) && (*tmp == '\0')) { + targetuser = l; + } else { + syslog(LOG_WARNING, "pam_xauth: invalid value " + "for targetuser (`%s')", argv[i] + 11); + } + continue; + } + if (strncmp(argv[i], "systemuser=", 11) == 0) { + long l = strtol(argv[i] + 11, &tmp, 10); + if ((strlen(argv[i] + 11) > 0) && (*tmp == '\0')) { + systemuser = l; + } else { + syslog(LOG_WARNING, "pam_xauth: invalid value " + "for systemuser (`%s')", argv[i] + 11); + } + continue; + } + syslog(LOG_WARNING, "pam_xauth: unrecognized option `%s'", + argv[i]); + } + + /* If DISPLAY isn't set, we don't really care, now do we? */ + if ((display = getenv("DISPLAY")) == NULL) { + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: user has no DISPLAY," + " doing nothing"); + } + return PAM_SUCCESS; + } + + /* Read the target user's name. */ + if (pam_get_item(pamh, PAM_USER, (const void**)&user) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_xauth: error determining target " + "user's name"); + return PAM_SESSION_ERR; + } + rpwd = _pammodutil_getpwuid(pamh, getuid()); + if (rpwd == NULL) { + syslog(LOG_ERR, "pam_xauth: error determining invoking " + "user's name"); + return PAM_SESSION_ERR; + } + + /* Get the target user's UID and primary GID, which we'll need to set + * on the xauthority file we create later on. */ + tpwd = _pammodutil_getpwnam(pamh, user); + if (tpwd == NULL) { + syslog(LOG_ERR, "pam_xauth: error determining target " + "user's UID"); + return PAM_SESSION_ERR; + } + + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: requesting user %lu/%lu, " + "target user %lu/%lu", + (unsigned long) rpwd->pw_uid, + (unsigned long) rpwd->pw_gid, + (unsigned long) tpwd->pw_uid, + (unsigned long) tpwd->pw_gid); + } + + /* If the UID is a system account (and not the superuser), forget + * about forwarding keys. */ + if ((tpwd->pw_uid != 0) && + (tpwd->pw_uid != targetuser) && + (tpwd->pw_uid <= systemuser)) { + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: not forwarding cookies " + "to user ID %ld", (long) tpwd->pw_uid); + } + return PAM_SESSION_ERR; + } + + /* Check that both users are amenable to this. By default, this + * boils down to this policy: + * export(ruser=root): only if <user> is listed in .xauth/export + * export(ruser=*) if <user> is listed in .xauth/export, or + * if .xauth/export does not exist + * import(user=*): if <ruser> is listed in .xauth/import, or + * if .xauth/import does not exist */ + i = (getuid() != 0) ? PAM_SUCCESS : PAM_PERM_DENIED; + i = check_acl(pamh, "export", rpwd->pw_name, user, i, debug); + if (i != PAM_SUCCESS) { + return PAM_SESSION_ERR; + } + i = PAM_SUCCESS; + i = check_acl(pamh, "import", user, rpwd->pw_name, i, debug); + if (i != PAM_SUCCESS) { + return PAM_SESSION_ERR; + } + + /* Figure out where the source user's .Xauthority file is. */ + if (getenv(XAUTHENV) != NULL) { + cookiefile = strdup(getenv(XAUTHENV)); + } else { + cookiefile = malloc(strlen(rpwd->pw_dir) + 1 + + strlen(XAUTHDEF) + 1); + if (cookiefile == NULL) { + return PAM_SESSION_ERR; + } + strcpy(cookiefile, rpwd->pw_dir); + strcat(cookiefile, "/"); + strcat(cookiefile, XAUTHDEF); + } + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: reading keys from `%s'", + cookiefile); + } + + /* Read the user's .Xauthority file. Because the current UID is + * the original user's UID, this will only fail if something has + * gone wrong, or we have no cookies. */ + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: running \"%s %s %s %s %s\" as " + "%lu/%lu", + xauth, + "-f", + cookiefile, + "nlist", + display, + (unsigned long) getuid(), + (unsigned long) getgid()); + } + if (run_coprocess(NULL, &cookie, + getuid(), getgid(), + xauth, "-f", cookiefile, "nlist", display, + NULL) == 0) { + /* Check that we got a cookie. If not, we get creative. */ + if (((cookie == NULL) || (strlen(cookie) == 0)) && + ((strncmp(display, "localhost:", 10) == 0) || + (strncmp(display, "localhost/unix:", 15) == 0))) { + char *t, *screen; + size_t tlen, slen; + /* Free the useless cookie string. */ + if (cookie != NULL) { + free(cookie); + cookie = NULL; + } + /* Allocate enough space to hold an adjusted name. */ + tlen = strlen(display) + LINE_MAX + 1; + t = malloc(tlen); + if (t != NULL) { + memset(t, 0, tlen); + if (gethostname(t, tlen - 1) != -1) { + /* Append the protocol and then the + * screen number. */ + if (strlen(t) < tlen - 6) { + strcat(t, "/unix:"); + } + screen = strchr(display, ':'); + if (screen != NULL) { + screen++; + slen = strlen(screen); + if (strlen(t) + slen < tlen) { + strcat(t, screen); + } + } + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: " + "no key for `%s', trying" + " `%s'", display, t); + } + /* Read the cookie for this display. */ + if (debug) { + syslog(LOG_DEBUG, + "pam_xauth: running " + "\"%s %s %s %s %s\" as " + "%lu/%lu", + xauth, + "-f", + cookiefile, + "nlist", + t, + (unsigned long) getuid(), + (unsigned long) getgid()); + } + run_coprocess(NULL, &cookie, + getuid(), getgid(), + xauth, "-f", cookiefile, + "nlist", t, NULL); + } + free(t); + t = NULL; + } + } + + /* Check that we got a cookie, this time for real. */ + if ((cookie == NULL) || (strlen(cookie) == 0)) { + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: no key"); + } + return PAM_SESSION_ERR; + } + + /* Generate the environment variable + * "XAUTHORITY=<homedir>/filename". */ + xauthority = malloc(strlen(XAUTHENV) + 1 + + strlen(tpwd->pw_dir) + 1 + + strlen(XAUTHTMP) + 1); + if (xauthority == NULL) { + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: no free memory"); + } + free(cookiefile); + free(cookie); + return PAM_SESSION_ERR; + } + strcpy(xauthority, XAUTHENV); + strcat(xauthority, "="); + strcat(xauthority, tpwd->pw_dir); + strcat(xauthority, "/"); + strcat(xauthority, XAUTHTMP); + + /* Generate a new file to hold the data. */ + euid = geteuid(); + setfsuid(tpwd->pw_uid); + fd = mkstemp(xauthority + strlen(XAUTHENV) + 1); + setfsuid(euid); + if (fd == -1) { + syslog(LOG_ERR, "pam_xauth: error creating " + "temporary file `%s': %s", + xauthority + strlen(XAUTHENV) + 1, + strerror(errno)); + free(cookiefile); + free(cookie); + free(xauthority); + return PAM_SESSION_ERR; + } + /* Set permissions on the new file and dispose of the + * descriptor. */ + fchown(fd, tpwd->pw_uid, tpwd->pw_gid); + close(fd); + + /* Get a copy of the filename to save as a data item for + * removal at session-close time. */ + free(cookiefile); + cookiefile = strdup(xauthority + strlen(XAUTHENV) + 1); + + /* Save the filename. */ + if (pam_set_data(pamh, DATANAME, cookiefile, cleanup) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_xauth: error saving name of " + "temporary file `%s'", cookiefile); + unlink(cookiefile); + free(xauthority); + free(cookiefile); + free(cookie); + return PAM_SESSION_ERR; + } + + /* Unset any old XAUTHORITY variable in the environment. */ + if (getenv(XAUTHENV)) { + unsetenv(XAUTHENV); + } + + /* Set the new variable in the environment. */ + pam_putenv(pamh, xauthority); + putenv(xauthority); /* The environment owns this string now. */ + + /* Merge the cookie we read before into the new file. */ + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: writing key `%s' to " + "temporary file `%s'", cookie, cookiefile); + } + if (debug) { + syslog(LOG_DEBUG, + "pam_xauth: running \"%s %s %s %s %s\" as " + "%lu/%lu", + xauth, + "-f", + cookiefile, + "nmerge", + "-", + (unsigned long) tpwd->pw_uid, + (unsigned long) tpwd->pw_gid); + } + run_coprocess(cookie, &tmp, + tpwd->pw_uid, tpwd->pw_gid, + xauth, "-f", cookiefile, "nmerge", "-", NULL); + + /* We don't need to keep a copy of these around any more. */ + free(cookie); + cookie = NULL; + } + return PAM_SUCCESS; +} + +int +pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + void *cookiefile; + int i, debug = 0; + + /* Parse arguments. We don't understand many, so no sense in breaking + * this into a separate function. */ + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug = 1; + continue; + } + if (strncmp(argv[i], "xauthpath=", 10) == 0) { + continue; + } + if (strncmp(argv[i], "systemuser=", 11) == 0) { + continue; + } + if (strncmp(argv[i], "targetuser=", 11) == 0) { + continue; + } + syslog(LOG_WARNING, "pam_xauth: unrecognized option `%s'", + argv[i]); + } + + /* Try to retrieve the name of a file we created when the session was + * opened. */ + if (pam_get_data(pamh, DATANAME, (const void**) &cookiefile) == PAM_SUCCESS) { + /* We'll only try to remove the file once. */ + if (strlen((char*)cookiefile) > 0) { + if (debug) { + syslog(LOG_DEBUG, "pam_xauth: removing `%s'", + (char*)cookiefile); + } + unlink((char*)cookiefile); + *((char*)cookiefile) = '\0'; + } + } + return PAM_SUCCESS; +} |