2 * Copyright 2001-2003 Red Hat, Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, and the entire permission notice in its entirety,
9 * including the disclaimer of warranties.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote
14 * products derived from this software without specific prior
17 * ALTERNATIVELY, this product may be distributed under the terms of
18 * the GNU Public License, in which case the provisions of the GPL are
19 * required INSTEAD OF the above restrictions. (This clause is
20 * necessary due to a potential bad interaction between the GPL and
21 * the restrictions contained in a BSD-style copyright.)
23 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
24 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33 * OF THE POSSIBILITY OF SUCH DAMAGE.
37 #include <sys/types.h>
38 #include <sys/fsuid.h>
53 #define PAM_SM_SESSION
55 #include <security/pam_modules.h>
56 #include <security/_pam_macros.h>
57 #include <security/pam_modutil.h>
58 #include <security/pam_ext.h>
60 #define DATANAME "pam_xauth_cookie_file"
61 #define XAUTHENV "XAUTHORITY"
62 #define HOMEENV "HOME"
63 #define XAUTHDEF ".Xauthority"
64 #define XAUTHTMP ".xauthXXXXXX"
66 /* Possible paths to xauth executable */
67 static const char * const xauthpaths[] = {
71 "/usr/X11R6/bin/xauth",
76 /* Run a given command (with a NULL-terminated argument list), feeding it the
77 * given input on stdin, and storing any output it generates. */
79 run_coprocess(const char *input, char **output,
80 uid_t uid, gid_t gid, const char *command, ...)
82 int ipipe[2], opipe[2], i;
86 size_t buffer_size = 0;
91 /* Create stdio pipery. */
92 if (pipe(ipipe) == -1) {
95 if (pipe(opipe) == -1) {
101 /* Fork off a child. */
112 /* We're the child. */
116 /* Drop privileges. */
120 /* Initialize the argument list. */
121 memset(args, 0, sizeof(args));
122 /* Set the pipe descriptors up as stdin and stdout, and close
123 * everything else, including the original values for the
125 dup2(ipipe[0], STDIN_FILENO);
126 dup2(opipe[1], STDOUT_FILENO);
127 for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) {
128 if ((i != STDIN_FILENO) && (i != STDOUT_FILENO)) {
132 /* Convert the varargs list into a regular array of strings. */
133 va_start(ap, command);
134 args[0] = strdup(command);
135 for (j = 1; j < ((sizeof(args) / sizeof(args[0])) - 1); j++) {
136 tmp = va_arg(ap, const char*);
140 args[j] = strdup(tmp);
142 /* Run the command. */
143 execv(command, args);
148 /* We're the parent, so close the other ends of the pipes. */
151 /* Send input to the process (if we have any), then send an EOF. */
153 (void)pam_modutil_write(ipipe[1], input, strlen(input));
157 /* Read data output until we run out of stuff to read. */
158 i = pam_modutil_read(opipe[0], buf, sizeof(buf));
159 while ((i != 0) && (i != -1)) {
161 /* Resize the buffer to hold the data. */
162 tmp = realloc(buffer, buffer_size + i + 1);
165 if (buffer != NULL) {
169 waitpid(child, NULL, 0);
172 /* Save the new buffer location, copy the newly-read data into
173 * the buffer, and make sure the result will be
176 memcpy(buffer + buffer_size, buf, i);
177 buffer[buffer_size + i] = '\0';
179 /* Try to read again. */
180 i = pam_modutil_read(opipe[0], buf, sizeof(buf));
182 /* No more data. Clean up and return data. */
185 waitpid(child, NULL, 0);
189 /* Free a data item. */
191 cleanup (pam_handle_t *pamh UNUSED, void *data, int err UNUSED)
196 /* Check if we want to allow export to the other user, or import from the
199 check_acl(pam_handle_t *pamh,
200 const char *sense, const char *this_user, const char *other_user,
201 int noent_code, int debug)
208 /* Check this user's <sense> file. */
209 pwd = pam_modutil_getpwnam(pamh, this_user);
211 pam_syslog(pamh, LOG_ERR,
212 "error determining home directory for '%s'",
214 return PAM_SESSION_ERR;
216 /* Figure out what that file is really named. */
217 i = snprintf(path, sizeof(path), "%s/.xauth/%s", pwd->pw_dir, sense);
218 if ((i >= (int)sizeof(path)) || (i < 0)) {
219 pam_syslog(pamh, LOG_ERR,
220 "name of user's home directory is too long");
221 return PAM_SESSION_ERR;
224 setfsuid(pwd->pw_uid);
225 fp = fopen(path, "r");
228 char buf[LINE_MAX], *tmp;
229 /* Scan the file for a list of specs of users to "trust". */
230 while (fgets(buf, sizeof(buf), fp) != NULL) {
231 tmp = memchr(buf, '\r', sizeof(buf));
235 tmp = memchr(buf, '\n', sizeof(buf));
239 if (fnmatch(buf, other_user, 0) == 0) {
241 pam_syslog(pamh, LOG_DEBUG,
242 "%s %s allowed by %s",
243 other_user, sense, path);
249 /* If there's no match in the file, we fail. */
251 pam_syslog(pamh, LOG_DEBUG, "%s not listed in %s",
255 return PAM_PERM_DENIED;
257 /* Default to okay if the file doesn't exist. */
260 if (noent_code == PAM_SUCCESS) {
262 pam_syslog(pamh, LOG_DEBUG,
263 "%s does not exist, ignoring",
268 pam_syslog(pamh, LOG_DEBUG,
269 "%s does not exist, failing",
276 pam_syslog(pamh, LOG_ERR,
277 "error opening %s: %m", path);
279 return PAM_PERM_DENIED;
285 pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
286 int argc, const char **argv)
288 char *cookiefile = NULL, *xauthority = NULL,
289 *cookie = NULL, *display = NULL, *tmp = NULL;
290 const char *user, *xauth = NULL;
291 struct passwd *tpwd, *rpwd;
292 int fd, i, debug = 0;
293 int retval = PAM_SUCCESS;
294 uid_t systemuser = 499, targetuser = 0, euid;
296 /* Parse arguments. We don't understand many, so no sense in breaking
297 * this into a separate function. */
298 for (i = 0; i < argc; i++) {
299 if (strcmp(argv[i], "debug") == 0) {
303 if (strncmp(argv[i], "xauthpath=", 10) == 0) {
304 xauth = argv[i] + 10;
307 if (strncmp(argv[i], "targetuser=", 11) == 0) {
308 long l = strtol(argv[i] + 11, &tmp, 10);
309 if ((strlen(argv[i] + 11) > 0) && (*tmp == '\0')) {
312 pam_syslog(pamh, LOG_WARNING,
313 "invalid value for targetuser (`%s')",
318 if (strncmp(argv[i], "systemuser=", 11) == 0) {
319 long l = strtol(argv[i] + 11, &tmp, 10);
320 if ((strlen(argv[i] + 11) > 0) && (*tmp == '\0')) {
323 pam_syslog(pamh, LOG_WARNING,
324 "invalid value for systemuser (`%s')",
329 pam_syslog(pamh, LOG_WARNING, "unrecognized option `%s'",
335 for (j = 0; j < sizeof(xauthpaths)/sizeof(xauthpaths[0]); j++) {
336 if (access(xauthpaths[j], X_OK) == 0) {
337 xauth = xauthpaths[j];
342 /* xauth executable not found - nothing to do */
347 /* If DISPLAY isn't set, we don't really care, now do we? */
348 if ((display = getenv("DISPLAY")) == NULL) {
350 pam_syslog(pamh, LOG_DEBUG,
351 "user has no DISPLAY, doing nothing");
356 /* Read the target user's name. */
357 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) {
358 pam_syslog(pamh, LOG_ERR,
359 "error determining target user's name");
360 retval = PAM_SESSION_ERR;
363 rpwd = pam_modutil_getpwuid(pamh, getuid());
365 pam_syslog(pamh, LOG_ERR,
366 "error determining invoking user's name");
367 retval = PAM_SESSION_ERR;
371 /* Get the target user's UID and primary GID, which we'll need to set
372 * on the xauthority file we create later on. */
373 tpwd = pam_modutil_getpwnam(pamh, user);
375 pam_syslog(pamh, LOG_ERR,
376 "error determining target user's UID");
377 retval = PAM_SESSION_ERR;
382 pam_syslog(pamh, LOG_DEBUG,
383 "requesting user %lu/%lu, target user %lu/%lu",
384 (unsigned long) rpwd->pw_uid,
385 (unsigned long) rpwd->pw_gid,
386 (unsigned long) tpwd->pw_uid,
387 (unsigned long) tpwd->pw_gid);
390 /* If the UID is a system account (and not the superuser), forget
391 * about forwarding keys. */
392 if ((tpwd->pw_uid != 0) &&
393 (tpwd->pw_uid != targetuser) &&
394 (tpwd->pw_uid <= systemuser)) {
396 pam_syslog(pamh, LOG_DEBUG,
397 "not forwarding cookies to user ID %lu",
398 (unsigned long) tpwd->pw_uid);
400 retval = PAM_SESSION_ERR;
404 /* Check that both users are amenable to this. By default, this
405 * boils down to this policy:
406 * export(ruser=root): only if <user> is listed in .xauth/export
407 * export(ruser=*) if <user> is listed in .xauth/export, or
408 * if .xauth/export does not exist
409 * import(user=*): if <ruser> is listed in .xauth/import, or
410 * if .xauth/import does not exist */
411 i = (getuid() != 0 || tpwd->pw_uid == 0) ? PAM_SUCCESS : PAM_PERM_DENIED;
412 i = check_acl(pamh, "export", rpwd->pw_name, user, i, debug);
413 if (i != PAM_SUCCESS) {
414 retval = PAM_SESSION_ERR;
418 i = check_acl(pamh, "import", user, rpwd->pw_name, i, debug);
419 if (i != PAM_SUCCESS) {
420 retval = PAM_SESSION_ERR;
424 /* Figure out where the source user's .Xauthority file is. */
425 if (getenv(XAUTHENV) != NULL) {
426 cookiefile = strdup(getenv(XAUTHENV));
428 cookiefile = malloc(strlen(rpwd->pw_dir) + 1 +
429 strlen(XAUTHDEF) + 1);
430 if (cookiefile == NULL) {
431 retval = PAM_SESSION_ERR;
434 strcpy(cookiefile, rpwd->pw_dir);
435 strcat(cookiefile, "/");
436 strcat(cookiefile, XAUTHDEF);
439 pam_syslog(pamh, LOG_DEBUG, "reading keys from `%s'",
443 /* Read the user's .Xauthority file. Because the current UID is
444 * the original user's UID, this will only fail if something has
445 * gone wrong, or we have no cookies. */
447 pam_syslog(pamh, LOG_DEBUG,
448 "running \"%s %s %s %s %s\" as %lu/%lu",
449 xauth, "-f", cookiefile, "nlist", display,
450 (unsigned long) getuid(), (unsigned long) getgid());
452 if (run_coprocess(NULL, &cookie,
454 xauth, "-f", cookiefile, "nlist", display,
456 /* Check that we got a cookie. If not, we get creative. */
457 if (((cookie == NULL) || (strlen(cookie) == 0)) &&
458 ((strncmp(display, "localhost:", 10) == 0) ||
459 (strncmp(display, "localhost/unix:", 15) == 0))) {
462 /* Free the useless cookie string. */
463 if (cookie != NULL) {
467 /* Allocate enough space to hold an adjusted name. */
468 tlen = strlen(display) + LINE_MAX + 1;
472 if (gethostname(t, tlen - 1) != -1) {
473 /* Append the protocol and then the
475 if (strlen(t) < tlen - 6) {
478 screen = strchr(display, ':');
479 if (screen != NULL) {
481 slen = strlen(screen);
482 if (strlen(t) + slen < tlen) {
487 pam_syslog(pamh, LOG_DEBUG,
492 /* Read the cookie for this display. */
494 pam_syslog(pamh, LOG_DEBUG,
496 "\"%s %s %s %s %s\" as "
503 (unsigned long) getuid(),
504 (unsigned long) getgid());
506 run_coprocess(NULL, &cookie,
508 xauth, "-f", cookiefile,
516 /* Check that we got a cookie, this time for real. */
517 if ((cookie == NULL) || (strlen(cookie) == 0)) {
519 pam_syslog(pamh, LOG_DEBUG, "no key");
521 retval = PAM_SESSION_ERR;
525 /* Generate the environment variable
526 * "XAUTHORITY=<homedir>/filename". */
527 if (asprintf(&xauthority, "%s=%s/%s",
528 XAUTHENV, tpwd->pw_dir, XAUTHTMP) < 0) {
531 pam_syslog(pamh, LOG_DEBUG, "out of memory");
533 retval = PAM_SESSION_ERR;
537 /* Generate a new file to hold the data. */
539 setfsuid(tpwd->pw_uid);
540 fd = mkstemp(xauthority + strlen(XAUTHENV) + 1);
543 pam_syslog(pamh, LOG_ERR,
544 "error creating temporary file `%s': %m",
545 xauthority + strlen(XAUTHENV) + 1);
546 retval = PAM_SESSION_ERR;
549 /* Set permissions on the new file and dispose of the
551 if (fchown(fd, tpwd->pw_uid, tpwd->pw_gid) < 0)
552 pam_syslog (pamh, LOG_ERR, "fchown: %m");
555 /* Get a copy of the filename to save as a data item for
556 * removal at session-close time. */
558 cookiefile = strdup(xauthority + strlen(XAUTHENV) + 1);
560 /* Save the filename. */
561 if (pam_set_data(pamh, DATANAME, cookiefile, cleanup) != PAM_SUCCESS) {
562 pam_syslog(pamh, LOG_ERR,
563 "error saving name of temporary file `%s'",
566 retval = PAM_SESSION_ERR;
570 /* Set the new variable in the environment. */
571 if (pam_putenv (pamh, xauthority) != PAM_SUCCESS)
572 pam_syslog(pamh, LOG_ERR,
573 "can't set environment variable '%s'",
575 putenv (xauthority); /* The environment owns this string now. */
577 /* set $DISPLAY in pam handle to make su - work */
581 if (asprintf(&d, "DISPLAY=%s", display) < 0)
583 pam_syslog(pamh, LOG_DEBUG, "out of memory");
585 retval = PAM_SESSION_ERR;
589 if (pam_putenv (pamh, d) != PAM_SUCCESS)
590 pam_syslog (pamh, LOG_DEBUG,
591 "can't set environment variable '%s'", d);
595 /* Merge the cookie we read before into the new file. */
597 pam_syslog(pamh, LOG_DEBUG,
598 "writing key `%s' to temporary file `%s'",
602 pam_syslog(pamh, LOG_DEBUG,
603 "running \"%s %s %s %s %s\" as %lu/%lu",
604 xauth, "-f", cookiefile, "nmerge", "-",
605 (unsigned long) tpwd->pw_uid,
606 (unsigned long) tpwd->pw_gid);
608 run_coprocess(cookie, &tmp,
609 tpwd->pw_uid, tpwd->pw_gid,
610 xauth, "-f", cookiefile, "nmerge", "-", NULL);
612 /* We don't need to keep a copy of these around any more. */
617 /* Unset any old XAUTHORITY variable in the environment. */
618 if (retval != PAM_SUCCESS && getenv (XAUTHENV))
627 pam_sm_close_session (pam_handle_t *pamh, int flags UNUSED,
628 int argc, const char **argv)
633 /* Parse arguments. We don't understand many, so no sense in breaking
634 * this into a separate function. */
635 for (i = 0; i < argc; i++) {
636 if (strcmp(argv[i], "debug") == 0) {
640 if (strncmp(argv[i], "xauthpath=", 10) == 0) {
643 if (strncmp(argv[i], "systemuser=", 11) == 0) {
646 if (strncmp(argv[i], "targetuser=", 11) == 0) {
649 pam_syslog(pamh, LOG_WARNING, "unrecognized option `%s'",
653 /* Try to retrieve the name of a file we created when the session was
655 if (pam_get_data(pamh, DATANAME, (const void**) &cookiefile) == PAM_SUCCESS) {
656 /* We'll only try to remove the file once. */
657 if (strlen((char*)cookiefile) > 0) {
659 pam_syslog(pamh, LOG_DEBUG, "removing `%s'",
662 unlink((char*)cookiefile);
663 *((char*)cookiefile) = '\0';
669 /* static module data */
671 struct pam_module _pam_xauth_modstruct = {
677 pam_sm_close_session,