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>
55 #define PAM_SM_SESSION
57 #include <security/pam_modules.h>
58 #include <security/_pam_macros.h>
59 #include <security/pam_modutil.h>
60 #include <security/pam_ext.h>
63 #include <selinux/selinux.h>
64 #include <selinux/label.h>
68 #define DATANAME "pam_xauth_cookie_file"
69 #define XAUTHENV "XAUTHORITY"
70 #define HOMEENV "HOME"
71 #define XAUTHDEF ".Xauthority"
72 #define XAUTHTMP ".xauthXXXXXX"
74 /* Hurd compatibility */
79 /* Possible paths to xauth executable */
80 static const char * const xauthpaths[] = {
84 "/usr/X11R6/bin/xauth",
89 /* Run a given command (with a NULL-terminated argument list), feeding it the
90 * given input on stdin, and storing any output it generates. */
92 run_coprocess(pam_handle_t *pamh, const char *input, char **output,
93 uid_t uid, gid_t gid, const char *command, ...)
95 int ipipe[2], opipe[2], i;
99 size_t buffer_size = 0;
104 /* Create stdio pipery. */
105 if (pipe(ipipe) == -1) {
106 pam_syslog(pamh, LOG_ERR, "Could not create pipe: %m");
109 if (pipe(opipe) == -1) {
110 pam_syslog(pamh, LOG_ERR, "Could not create pipe: %m");
116 /* Fork off a child. */
119 pam_syslog(pamh, LOG_ERR, "Could not fork: %m");
128 /* We're the child. */
130 const char *args[10];
132 /* Drop privileges. */
133 if (setgid(gid) == -1)
136 pam_syslog (pamh, LOG_ERR, "setgid(%lu) failed: %m",
137 (unsigned long) getegid ());
140 if (setgroups(0, NULL) == -1)
143 pam_syslog (pamh, LOG_ERR, "setgroups() failed: %m");
146 if (setuid(uid) == -1)
149 pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m",
150 (unsigned long) geteuid ());
153 /* Initialize the argument list. */
154 memset(args, 0, sizeof(args));
155 /* Set the pipe descriptors up as stdin and stdout, and close
156 * everything else, including the original values for the
158 dup2(ipipe[0], STDIN_FILENO);
159 dup2(opipe[1], STDOUT_FILENO);
160 maxopened = (int)sysconf(_SC_OPEN_MAX);
161 for (i = 0; i < maxopened; i++) {
162 if ((i != STDIN_FILENO) && (i != STDOUT_FILENO)) {
166 /* Convert the varargs list into a regular array of strings. */
167 va_start(ap, command);
169 for (j = 1; j < ((sizeof(args) / sizeof(args[0])) - 1); j++) {
170 args[j] = va_arg(ap, const char*);
171 if (args[j] == NULL) {
175 /* Run the command. */
176 execv(command, (char *const *) args);
181 /* We're the parent, so close the other ends of the pipes. */
184 /* Send input to the process (if we have any), then send an EOF. */
186 (void)pam_modutil_write(ipipe[1], input, strlen(input));
190 /* Read data output until we run out of stuff to read. */
191 i = pam_modutil_read(opipe[0], buf, sizeof(buf));
192 while ((i != 0) && (i != -1)) {
194 /* Resize the buffer to hold the data. */
195 tmp = realloc(buffer, buffer_size + i + 1);
198 if (buffer != NULL) {
202 waitpid(child, NULL, 0);
205 /* Save the new buffer location, copy the newly-read data into
206 * the buffer, and make sure the result will be
209 memcpy(buffer + buffer_size, buf, i);
210 buffer[buffer_size + i] = '\0';
212 /* Try to read again. */
213 i = pam_modutil_read(opipe[0], buf, sizeof(buf));
215 /* No more data. Clean up and return data. */
218 waitpid(child, NULL, 0);
222 /* Free a data item. */
224 cleanup (pam_handle_t *pamh UNUSED, void *data, int err UNUSED)
229 /* Check if we want to allow export to the other user, or import from the
232 check_acl(pam_handle_t *pamh,
233 const char *sense, const char *this_user, const char *other_user,
234 int noent_code, int debug)
239 int i, fd = -1, save_errno;
241 PAM_MODUTIL_DEF_PRIVS(privs);
243 /* Check this user's <sense> file. */
244 pwd = pam_modutil_getpwnam(pamh, this_user);
246 pam_syslog(pamh, LOG_ERR,
247 "error determining home directory for '%s'",
249 return PAM_SESSION_ERR;
251 /* Figure out what that file is really named. */
252 i = snprintf(path, sizeof(path), "%s/.xauth/%s", pwd->pw_dir, sense);
253 if ((i >= (int)sizeof(path)) || (i < 0)) {
254 pam_syslog(pamh, LOG_ERR,
255 "name of user's home directory is too long");
256 return PAM_SESSION_ERR;
258 if (pam_modutil_drop_priv(pamh, &privs, pwd))
259 return PAM_SESSION_ERR;
260 if (!stat(path, &st)) {
261 if (!S_ISREG(st.st_mode))
264 fd = open(path, O_RDONLY | O_NOCTTY);
267 if (pam_modutil_regain_priv(pamh, &privs)) {
270 return PAM_SESSION_ERR;
273 if (!fstat(fd, &st)) {
274 if (!S_ISREG(st.st_mode))
277 fp = fdopen(fd, "r");
285 char buf[LINE_MAX], *tmp;
286 /* Scan the file for a list of specs of users to "trust". */
287 while (fgets(buf, sizeof(buf), fp) != NULL) {
288 tmp = memchr(buf, '\r', sizeof(buf));
292 tmp = memchr(buf, '\n', sizeof(buf));
296 if (fnmatch(buf, other_user, 0) == 0) {
298 pam_syslog(pamh, LOG_DEBUG,
299 "%s %s allowed by %s",
300 other_user, sense, path);
306 /* If there's no match in the file, we fail. */
308 pam_syslog(pamh, LOG_DEBUG, "%s not listed in %s",
312 return PAM_PERM_DENIED;
314 /* Default to okay if the file doesn't exist. */
318 if (noent_code == PAM_SUCCESS) {
320 pam_syslog(pamh, LOG_DEBUG,
321 "%s does not exist, ignoring",
326 pam_syslog(pamh, LOG_DEBUG,
327 "%s does not exist, failing",
334 pam_syslog(pamh, LOG_DEBUG,
335 "error opening %s: %m", path);
337 return PAM_PERM_DENIED;
343 pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
344 int argc, const char **argv)
346 char *cookiefile = NULL, *xauthority = NULL,
347 *cookie = NULL, *display = NULL, *tmp = NULL,
348 *xauthlocalhostname = NULL;
349 const char *user, *xauth = NULL;
350 struct passwd *tpwd, *rpwd;
351 int fd, i, debug = 0;
352 int retval = PAM_SUCCESS;
353 uid_t systemuser = 499, targetuser = 0;
355 /* Parse arguments. We don't understand many, so no sense in breaking
356 * this into a separate function. */
357 for (i = 0; i < argc; i++) {
358 if (strcmp(argv[i], "debug") == 0) {
362 if (strncmp(argv[i], "xauthpath=", 10) == 0) {
363 xauth = argv[i] + 10;
366 if (strncmp(argv[i], "targetuser=", 11) == 0) {
367 long l = strtol(argv[i] + 11, &tmp, 10);
368 if ((strlen(argv[i] + 11) > 0) && (*tmp == '\0')) {
371 pam_syslog(pamh, LOG_WARNING,
372 "invalid value for targetuser (`%s')",
377 if (strncmp(argv[i], "systemuser=", 11) == 0) {
378 long l = strtol(argv[i] + 11, &tmp, 10);
379 if ((strlen(argv[i] + 11) > 0) && (*tmp == '\0')) {
382 pam_syslog(pamh, LOG_WARNING,
383 "invalid value for systemuser (`%s')",
388 pam_syslog(pamh, LOG_WARNING, "unrecognized option `%s'",
394 for (j = 0; j < sizeof(xauthpaths)/sizeof(xauthpaths[0]); j++) {
395 if (access(xauthpaths[j], X_OK) == 0) {
396 xauth = xauthpaths[j];
401 /* xauth executable not found - nothing to do */
406 /* If DISPLAY isn't set, we don't really care, now do we? */
407 if ((display = getenv("DISPLAY")) == NULL) {
409 pam_syslog(pamh, LOG_DEBUG,
410 "user has no DISPLAY, doing nothing");
415 /* Read the target user's name. */
416 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) {
417 pam_syslog(pamh, LOG_ERR,
418 "error determining target user's name");
419 retval = PAM_SESSION_ERR;
422 rpwd = pam_modutil_getpwuid(pamh, getuid());
424 pam_syslog(pamh, LOG_ERR,
425 "error determining invoking user's name");
426 retval = PAM_SESSION_ERR;
430 /* Get the target user's UID and primary GID, which we'll need to set
431 * on the xauthority file we create later on. */
432 tpwd = pam_modutil_getpwnam(pamh, user);
434 pam_syslog(pamh, LOG_ERR,
435 "error determining target user's UID");
436 retval = PAM_SESSION_ERR;
441 pam_syslog(pamh, LOG_DEBUG,
442 "requesting user %lu/%lu, target user %lu/%lu",
443 (unsigned long) rpwd->pw_uid,
444 (unsigned long) rpwd->pw_gid,
445 (unsigned long) tpwd->pw_uid,
446 (unsigned long) tpwd->pw_gid);
449 /* If the UID is a system account (and not the superuser), forget
450 * about forwarding keys. */
451 if ((tpwd->pw_uid != 0) &&
452 (tpwd->pw_uid != targetuser) &&
453 (tpwd->pw_uid <= systemuser)) {
455 pam_syslog(pamh, LOG_DEBUG,
456 "not forwarding cookies to user ID %lu",
457 (unsigned long) tpwd->pw_uid);
459 retval = PAM_SESSION_ERR;
464 /* If current user and the target user are the same, don't
465 check the ACL list, but forward X11 */
466 if (strcmp (rpwd->pw_name, tpwd->pw_name) != 0) {
468 /* Check that both users are amenable to this. By default, this
469 * boils down to this policy:
470 * export(ruser=root): only if <user> is listed in .xauth/export
471 * export(ruser=*) if <user> is listed in .xauth/export, or
472 * if .xauth/export does not exist
473 * import(user=*): if <ruser> is listed in .xauth/import, or
474 * if .xauth/import does not exist */
475 i = (getuid() != 0 || tpwd->pw_uid == 0) ? PAM_SUCCESS : PAM_PERM_DENIED;
476 i = check_acl(pamh, "export", rpwd->pw_name, user, i, debug);
477 if (i != PAM_SUCCESS) {
478 retval = PAM_SESSION_ERR;
482 i = check_acl(pamh, "import", user, rpwd->pw_name, i, debug);
483 if (i != PAM_SUCCESS) {
484 retval = PAM_SESSION_ERR;
489 pam_syslog (pamh, LOG_DEBUG, "current and target user are the same, forward X11");
492 /* Figure out where the source user's .Xauthority file is. */
493 if (getenv(XAUTHENV) != NULL) {
494 cookiefile = strdup(getenv(XAUTHENV));
496 cookiefile = malloc(strlen(rpwd->pw_dir) + 1 +
497 strlen(XAUTHDEF) + 1);
498 if (cookiefile == NULL) {
499 retval = PAM_SESSION_ERR;
502 strcpy(cookiefile, rpwd->pw_dir);
503 strcat(cookiefile, "/");
504 strcat(cookiefile, XAUTHDEF);
507 pam_syslog(pamh, LOG_DEBUG, "reading keys from `%s'",
511 /* Read the user's .Xauthority file. Because the current UID is
512 * the original user's UID, this will only fail if something has
513 * gone wrong, or we have no cookies. */
515 pam_syslog(pamh, LOG_DEBUG,
516 "running \"%s %s %s %s %s\" as %lu/%lu",
517 xauth, "-f", cookiefile, "nlist", display,
518 (unsigned long) getuid(), (unsigned long) getgid());
520 if (run_coprocess(pamh, NULL, &cookie,
522 xauth, "-f", cookiefile, "nlist", display,
525 security_context_t context = NULL;
527 PAM_MODUTIL_DEF_PRIVS(privs);
529 /* Check that we got a cookie. If not, we get creative. */
530 if (((cookie == NULL) || (strlen(cookie) == 0)) &&
531 ((strncmp(display, "localhost:", 10) == 0) ||
532 (strncmp(display, "localhost/unix:", 15) == 0))) {
535 /* Free the useless cookie string. */
536 if (cookie != NULL) {
540 /* Allocate enough space to hold an adjusted name. */
541 tlen = strlen(display) + LINE_MAX + 1;
545 if (gethostname(t, tlen - 1) != -1) {
546 /* Append the protocol and then the
548 if (strlen(t) < tlen - 6) {
551 screen = strchr(display, ':');
552 if (screen != NULL) {
554 slen = strlen(screen);
555 if (strlen(t) + slen < tlen) {
560 pam_syslog(pamh, LOG_DEBUG,
565 /* Read the cookie for this display. */
567 pam_syslog(pamh, LOG_DEBUG,
569 "\"%s %s %s %s %s\" as "
576 (unsigned long) getuid(),
577 (unsigned long) getgid());
579 run_coprocess(pamh, NULL, &cookie,
581 xauth, "-f", cookiefile,
589 /* Check that we got a cookie, this time for real. */
590 if ((cookie == NULL) || (strlen(cookie) == 0)) {
592 pam_syslog(pamh, LOG_DEBUG, "no key");
594 retval = PAM_SESSION_ERR;
598 /* Generate the environment variable
599 * "XAUTHORITY=<homedir>/filename". */
600 if (asprintf(&xauthority, "%s=%s/%s",
601 XAUTHENV, tpwd->pw_dir, XAUTHTMP) < 0) {
604 pam_syslog(pamh, LOG_DEBUG, "out of memory");
606 retval = PAM_SESSION_ERR;
610 /* Generate a new file to hold the data. */
611 if (pam_modutil_drop_priv(pamh, &privs, tpwd)) {
612 retval = PAM_SESSION_ERR;
616 if (is_selinux_enabled() > 0) {
617 struct selabel_handle *ctx = selabel_open(SELABEL_CTX_FILE, NULL, 0);
619 if (selabel_lookup(ctx, &context,
620 xauthority + sizeof(XAUTHENV), S_IFREG) != 0) {
621 pam_syslog(pamh, LOG_WARNING,
622 "could not get SELinux label for '%s'",
623 xauthority + sizeof(XAUTHENV));
626 if (setfscreatecon(context)) {
627 pam_syslog(pamh, LOG_WARNING,
628 "setfscreatecon(%s) failed: %m", context);
632 #endif /* WITH_SELINUX */
633 fd = mkstemp(xauthority + sizeof(XAUTHENV));
635 pam_syslog(pamh, LOG_ERR,
636 "error creating temporary file `%s': %m",
637 xauthority + sizeof(XAUTHENV));
639 if (context != NULL) {
641 setfscreatecon(NULL);
643 #endif /* WITH_SELINUX */
646 if (pam_modutil_regain_priv(pamh, &privs) || fd < 0) {
647 retval = PAM_SESSION_ERR;
651 /* Get a copy of the filename to save as a data item for
652 * removal at session-close time. */
654 cookiefile = strdup(xauthority + sizeof(XAUTHENV));
656 /* Save the filename. */
657 if (pam_set_data(pamh, DATANAME, cookiefile, cleanup) != PAM_SUCCESS) {
658 pam_syslog(pamh, LOG_ERR,
659 "error saving name of temporary file `%s'",
662 retval = PAM_SESSION_ERR;
666 /* Set the new variable in the environment. */
667 if (pam_putenv (pamh, xauthority) != PAM_SUCCESS)
668 pam_syslog(pamh, LOG_ERR,
669 "can't set environment variable '%s'",
671 putenv (xauthority); /* The environment owns this string now. */
672 xauthority = NULL; /* Don't free environment variables. */
674 /* set $DISPLAY in pam handle to make su - work */
678 if (asprintf(&d, "DISPLAY=%s", display) < 0)
680 pam_syslog(pamh, LOG_ERR, "out of memory");
682 retval = PAM_SESSION_ERR;
686 if (pam_putenv (pamh, d) != PAM_SUCCESS)
687 pam_syslog (pamh, LOG_ERR,
688 "can't set environment variable '%s'", d);
692 /* set XAUTHLOCALHOSTNAME to make sure that su - work under gnome */
693 if ((xauthlocalhostname = getenv("XAUTHLOCALHOSTNAME")) != NULL) {
696 if (asprintf(&d, "XAUTHLOCALHOSTNAME=%s", xauthlocalhostname) < 0) {
697 pam_syslog(pamh, LOG_ERR, "out of memory");
698 retval = PAM_SESSION_ERR;
702 if (pam_putenv (pamh, d) != PAM_SUCCESS)
703 pam_syslog (pamh, LOG_ERR,
704 "can't set environment variable '%s'", d);
708 /* Merge the cookie we read before into the new file. */
710 pam_syslog(pamh, LOG_DEBUG,
711 "writing key `%s' to temporary file `%s'",
715 pam_syslog(pamh, LOG_DEBUG,
716 "running \"%s %s %s %s %s\" as %lu/%lu",
717 xauth, "-f", cookiefile, "nmerge", "-",
718 (unsigned long) tpwd->pw_uid,
719 (unsigned long) tpwd->pw_gid);
721 run_coprocess(pamh, cookie, &tmp,
722 tpwd->pw_uid, tpwd->pw_gid,
723 xauth, "-f", cookiefile, "nmerge", "-", NULL);
725 /* We don't need to keep a copy of these around any more. */
730 /* Unset any old XAUTHORITY variable in the environment. */
731 if (retval != PAM_SUCCESS && getenv (XAUTHENV))
740 pam_sm_close_session (pam_handle_t *pamh, int flags UNUSED,
741 int argc, const char **argv)
746 const char *cookiefile;
748 PAM_MODUTIL_DEF_PRIVS(privs);
750 /* Try to retrieve the name of a file we created when
751 * the session was opened. */
752 if (pam_get_data(pamh, DATANAME, &data) != PAM_SUCCESS)
756 /* Parse arguments. We don't understand many, so
757 * no sense in breaking this into a separate function. */
758 for (i = 0; i < argc; i++) {
759 if (strcmp(argv[i], "debug") == 0) {
763 if (strncmp(argv[i], "xauthpath=", 10) == 0)
765 if (strncmp(argv[i], "systemuser=", 11) == 0)
767 if (strncmp(argv[i], "targetuser=", 11) == 0)
769 pam_syslog(pamh, LOG_WARNING, "unrecognized option `%s'",
773 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) {
774 pam_syslog(pamh, LOG_ERR,
775 "error determining target user's name");
776 return PAM_SESSION_ERR;
778 if (!(tpwd = pam_modutil_getpwnam(pamh, user))) {
779 pam_syslog(pamh, LOG_ERR,
780 "error determining target user's UID");
781 return PAM_SESSION_ERR;
785 pam_syslog(pamh, LOG_DEBUG, "removing `%s'", cookiefile);
786 if (pam_modutil_drop_priv(pamh, &privs, tpwd))
787 return PAM_SESSION_ERR;
788 if (unlink(cookiefile) == -1 && errno != ENOENT)
789 pam_syslog(pamh, LOG_WARNING, "Couldn't remove `%s': %m", cookiefile);
790 if (pam_modutil_regain_priv(pamh, &privs))
791 return PAM_SESSION_ERR;
796 /* static module data */
798 struct pam_module _pam_xauth_modstruct = {
804 pam_sm_close_session,