all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Paul Eggert <eggert@cs.ucla.edu>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 12632@debbugs.gnu.org
Subject: bug#12632: file permissions checking mishandled when setuid
Date: Sun, 14 Oct 2012 11:14:39 -0700	[thread overview]
Message-ID: <507B010F.20105@cs.ucla.edu> (raw)
In-Reply-To: <83fw5h5yo6.fsf@gnu.org>

[-- Attachment #1: Type: text/plain, Size: 7053 bytes --]

On 10/13/2012 11:56 PM, Eli Zaretskii wrote:

> This won't compile on Windows, since there's no 'euidaccess' (yet).

Thanks, that should be easy enough to fix, so I gave it a whirl
(patch below).

> Emacs should be able to test whether a file exists even if it
> will be unable to access it later.

Emacs cannot do that.  What 'access' does is ask, "If Emacs were
to issue the seteuid system call, and change the effective user
ID to the real user ID, would Emacs then be able to see that the
file exists?"  This does not test whether the file exists; it tests
only whether Emacs could see that the file exists in a hypothetical
situation that never actually happens (because Emacs never issues
the seteuid system call).  But this isn't what is wanted here:
what is wanted is a test whether Emacs can currently see that the
file exists, and that is what euidaccess does.

> In any case, using 'euidaccess' here subtly changes the semantics of
> file-exists-p

Currently file-exists-p uses 'stat', and 'euidaccess' uses
the same access check that 'stat' does -- they both use the
effective uid.  So the semantics aren't changing here.

>> > 'dir' can't be nil there.
> file-name-directory can return nil

It's an absolute file name, so file-name-directory can't return nil.
(This point is moot now, since the patch doesn't remove the
unnecessary NILP check.)

Here's the incremental patch to add euidaccess to Windows.
I'm attaching the entire revised patch, relative to trunk
bzr 110544.

=== modified file 'nt/ChangeLog'
--- nt/ChangeLog	2012-10-08 14:14:22 +0000
+++ nt/ChangeLog	2012-10-14 18:06:01 +0000
@@ -1,3 +1,9 @@
+2012-10-14  Paul Eggert  <eggert@cs.ucla.edu>
+
+	Use euidaccess, not access, when checking file permissions (Bug#12632).
+	* inc/ms-w32.h (euidaccess): Define this, not 'access', since
+	the mainline code now uses euidaccess.
+
 2012-10-08  Juanma Barranquero  <lekktu@gmail.com>
 
 	* config.nt: Sync with autogen/config.in.

=== modified file 'nt/inc/ms-w32.h'
--- nt/inc/ms-w32.h	2012-09-30 21:36:42 +0000
+++ nt/inc/ms-w32.h	2012-10-14 18:06:01 +0000
@@ -160,8 +160,6 @@
 #endif
 
 /* Calls that are emulated or shadowed.  */
-#undef access
-#define access  sys_access
 #undef chdir
 #define chdir   sys_chdir
 #undef chmod
@@ -176,6 +174,7 @@
 #define dup     sys_dup
 #undef dup2
 #define dup2    sys_dup2
+#define euidaccess  sys_access
 #define fopen   sys_fopen
 #define link    sys_link
 #define localtime sys_localtime

=== modified file 'src/ChangeLog'
--- src/ChangeLog	2012-10-14 06:02:52 +0000
+++ src/ChangeLog	2012-10-14 18:06:01 +0000
@@ -5,14 +5,13 @@
 	(LIBES): Use it.
 	* callproc.c (init_callproc):
 	* charset.c (init_charset):
-	* fileio.c (check_existing) [!DOS_NT]:
-	(check_executable) [!DOS_NT && !HAVE_EUIDACCESS]:
+	* fileio.c (check_existing, check_executable):
 	* lread.c (openp, load_path_check):
 	* process.c (allocate_pty):
 	* xrdb.c (file_p):
 	Use euidaccess, not access.  Use symbolic names instead of integers
 	for the flags, as they're portable now.
-	* fileio.c (Ffile_readable_p) [!DOS_NT && !macintosh]:
+	* fileio.c (Ffile_readable_p):
 	Use euidaccess, not stat + open + close.
 	(file_directory_p): New function, which uses 'stat' on most places
 	but 'access' (for efficiency) if WINDOWSNT.
@@ -21,6 +20,7 @@
 	* lread.c (openp): When opening a file, use fstat rather than
 	stat, as that avoids a permissions race.  When not opening a file,
 	use file_directory_p rather than stat.
+	* msdos.c (init_environment, readlink): Use sys_access, not access.
 
 2012-10-13  Jan Djärv  <jan.h.d@swipnet.se>
 

=== modified file 'src/fileio.c'
--- src/fileio.c	2012-10-14 06:02:52 +0000
+++ src/fileio.c	2012-10-14 18:06:01 +0000
@@ -2424,16 +2424,7 @@
 bool
 check_existing (const char *filename)
 {
-#ifdef DOS_NT
-  /* The full emulation of Posix 'stat' is too expensive on
-     DOS/Windows, when all we want to know is whether the file exists.
-     So we use 'access' instead, which is much more lightweight.  */
-  /* FIXME: an euidaccess implementation should be added to the
-     DOS/Windows ports and this #ifdef branch should be removed.  */
-  return (access (filename, F_OK) >= 0);
-#else
   return euidaccess (filename, F_OK) == 0;
-#endif
 }
 
 /* Return true if file FILENAME exists and can be executed.  */
@@ -2441,16 +2432,7 @@
 static bool
 check_executable (char *filename)
 {
-#ifdef DOS_NT
-  /* FIXME: an euidaccess implementation should be added to the
-     DOS/Windows ports and this #ifdef branch should be removed.  */
-  struct stat st;
-  if (stat (filename, &st) < 0)
-    return 0;
-  return ((st.st_mode & S_IEXEC) != 0);
-#else /* not DOS_NT */
   return euidaccess (filename, X_OK) == 0;
-#endif /* not DOS_NT */
 }
 
 /* Return true if file FILENAME exists and can be written.  */
@@ -2546,16 +2528,7 @@
     return call2 (handler, Qfile_readable_p, absname);
 
   absname = ENCODE_FILE (absname);
-
-#if defined (DOS_NT) || defined (macintosh)
-  /* FIXME: an euidaccess implementation should be added to the
-     DOS/Windows ports and this #ifdef branch should be removed.  */
-  if (access (SDATA (absname), 0) == 0)
-    return Qt;
-  return Qnil;
-#else /* not DOS_NT and not macintosh */
   return euidaccess (SSDATA (absname), R_OK) == 0 ? Qt : Qnil;
-#endif /* not DOS_NT and not macintosh */
 }
 
 /* Having this before file-symlink-p mysteriously caused it to be forgotten
@@ -2592,7 +2565,7 @@
   /* The read-only attribute of the parent directory doesn't affect
      whether a file or directory can be created within it.  Some day we
      should check ACLs though, which do affect this.  */
-  return (access (SDATA (dir), D_OK) < 0) ? Qnil : Qt;
+  return file_directory_p (SDATA (dir)) ? Qt : Qnil;
 #else
   return (check_writable (!NILP (dir) ? SSDATA (dir) : "")
 	  ? Qt : Qnil);
@@ -2694,8 +2667,8 @@
 file_directory_p (char const *file)
 {
 #ifdef WINDOWSNT
-  /* 'access' is cheaper than 'stat'.  */
-  return access (file, D_OK) == 0;
+  /* This is cheaper than 'stat'.  */
+  return euidaccess (file, D_OK) == 0;
 #else
   struct stat st;
   return stat (file, &st) == 0 && S_ISDIR (st.st_mode);

=== modified file 'src/msdos.c'
--- src/msdos.c	2012-09-23 08:44:20 +0000
+++ src/msdos.c	2012-10-14 18:06:01 +0000
@@ -3557,7 +3557,7 @@
 	 read-only filesystem, like CD-ROM or a write-protected floppy.
 	 The only way to be really sure is to actually create a file and
 	 see if it succeeds.  But I think that's too much to ask.  */
-      if (tmp && access (tmp, D_OK) == 0)
+      if (tmp && sys_access (tmp, D_OK) == 0)
 	{
 	  setenv ("TMPDIR", tmp, 1);
 	  break;
@@ -3935,7 +3935,7 @@
 readlink (const char *name, char *dummy1, size_t dummy2)
 {
   /* `access' is much faster than `stat' on MS-DOS.  */
-  if (access (name, F_OK) == 0)
+  if (sys_access (name, F_OK) == 0)
     errno = EINVAL;
   return -1;
 }



[-- Attachment #2: euidaccess.txt --]
[-- Type: text/plain, Size: 49280 bytes --]

=== modified file 'ChangeLog'
--- ChangeLog	2012-10-11 11:29:47 +0000
+++ ChangeLog	2012-10-14 06:03:50 +0000
@@ -1,3 +1,12 @@
+2012-10-14  Paul Eggert  <eggert@cs.ucla.edu>
+
+	Use euidaccess, not access, when checking file permissions (Bug#12632).
+	* configure.ac (euidaccess): Remove check; gnulib does this for us now.
+	* lib/euidaccess.c, lib/getgroups.c, lib/group-member.c:
+	* lib/root-uid.h, lib/xalloc-oversized.h, m4/euidaccess.m4:
+	* m4/getgroups.m4, m4/group-member.m4: New files, from gnulib.
+	* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
+
 2012-10-11  Kenichi Handa  <handa@gnu.org>
 
 	* .bzrignore: Add several files under admin/charsets.

=== modified file 'admin/ChangeLog'
--- admin/ChangeLog	2012-10-14 08:01:30 +0000
+++ admin/ChangeLog	2012-10-14 18:08:30 +0000
@@ -1,3 +1,9 @@
+2012-10-14  Paul Eggert  <eggert@cs.ucla.edu>
+
+	Use euidaccess, not access, when checking file permissions (Bug#12632).
+	* merge-gnulib (GNULIB_MODULES): Add euidaccess.
+	(GNULIB_TOOL_FLAGS): Avoid malloc-posix.
+
 2012-10-12  Kenichi Handa  <handa@gnu.org>
 
 	* charsets/Makefile (JISC6226.map): Add missing mappings.

=== modified file 'admin/merge-gnulib'
--- admin/merge-gnulib	2012-09-27 01:06:23 +0000
+++ admin/merge-gnulib	2012-10-12 23:28:31 +0000
@@ -28,7 +28,7 @@
 GNULIB_MODULES='
   alloca-opt c-ctype c-strcase
   careadlinkat crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512
-  dtoastr dtotimespec dup2 environ execinfo
+  dtoastr dtotimespec dup2 environ euidaccess execinfo
   filemode getloadavg getopt-gnu gettime gettimeofday
   ignore-value intprops largefile lstat
   manywarnings mktime pselect pthread_sigmask readlink
@@ -40,7 +40,7 @@
 
 GNULIB_TOOL_FLAGS='
   --avoid=errno --avoid=fcntl --avoid=fcntl-h --avoid=fstat
-  --avoid=msvc-inval --avoid=msvc-nothrow
+  --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow
   --avoid=raise --avoid=select --avoid=sigprocmask --avoid=sys_types
   --avoid=threadlib
   --conditional-dependencies --import --no-changelog --no-vc-files

=== modified file 'configure.ac'
--- configure.ac	2012-10-08 16:09:00 +0000
+++ configure.ac	2012-10-12 23:28:31 +0000
@@ -2872,7 +2872,7 @@
 AC_CHECK_FUNCS(gethostname \
 closedir getrusage get_current_dir_name \
 lrand48 setsid \
-fpathconf select euidaccess getpagesize setlocale \
+fpathconf select getpagesize setlocale \
 utimes getrlimit setrlimit setpgid getcwd shutdown getaddrinfo \
 __fpending strsignal setitimer \
 sendto recvfrom getsockname getpeername getifaddrs freeifaddrs \

=== added file 'lib/euidaccess.c'
--- lib/euidaccess.c	1970-01-01 00:00:00 +0000
+++ lib/euidaccess.c	2012-10-12 23:28:31 +0000
@@ -0,0 +1,221 @@
+/* euidaccess -- check if effective user id can access file
+
+   Copyright (C) 1990-1991, 1995, 1998, 2000, 2003-2006, 2008-2012 Free
+   Software Foundation, Inc.
+
+   This file is part of the GNU C Library.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by David MacKenzie and Torbjorn Granlund.
+   Adapted for GNU C library by Roland McGrath.  */
+
+#ifndef _LIBC
+# include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "root-uid.h"
+
+#if HAVE_LIBGEN_H
+# include <libgen.h>
+#endif
+
+#include <errno.h>
+#ifndef __set_errno
+# define __set_errno(val) errno = (val)
+#endif
+
+#if defined EACCES && !defined EACCESS
+# define EACCESS EACCES
+#endif
+
+#ifndef F_OK
+# define F_OK 0
+# define X_OK 1
+# define W_OK 2
+# define R_OK 4
+#endif
+
+
+#ifdef _LIBC
+
+# define access __access
+# define getuid __getuid
+# define getgid __getgid
+# define geteuid __geteuid
+# define getegid __getegid
+# define group_member __group_member
+# define euidaccess __euidaccess
+# undef stat
+# define stat stat64
+
+#endif
+
+/* Return 0 if the user has permission of type MODE on FILE;
+   otherwise, return -1 and set 'errno'.
+   Like access, except that it uses the effective user and group
+   id's instead of the real ones, and it does not always check for read-only
+   file system, text busy, etc.  */
+
+int
+euidaccess (const char *file, int mode)
+{
+#if HAVE_FACCESSAT                   /* glibc, AIX 7, Solaris 11, Cygwin 1.7 */
+  return faccessat (AT_FDCWD, file, mode, AT_EACCESS);
+#elif defined EFF_ONLY_OK               /* IRIX, OSF/1, Interix */
+  return access (file, mode | EFF_ONLY_OK);
+#elif defined ACC_SELF                  /* AIX */
+  return accessx (file, mode, ACC_SELF);
+#elif HAVE_EACCESS                      /* FreeBSD */
+  return eaccess (file, mode);
+#else       /* Mac OS X, NetBSD, OpenBSD, HP-UX, Solaris, Cygwin, mingw, BeOS */
+
+  uid_t uid = getuid ();
+  gid_t gid = getgid ();
+  uid_t euid = geteuid ();
+  gid_t egid = getegid ();
+  struct stat stats;
+
+# if HAVE_DECL_SETREGID && PREFER_NONREENTRANT_EUIDACCESS
+
+  /* Define PREFER_NONREENTRANT_EUIDACCESS if you prefer euidaccess to
+     return the correct result even if this would make it
+     nonreentrant.  Define this only if your entire application is
+     safe even if the uid or gid might temporarily change.  If your
+     application uses signal handlers or threads it is probably not
+     safe.  */
+
+  if (mode == F_OK)
+    return stat (file, &stats);
+  else
+    {
+      int result;
+      int saved_errno;
+
+      if (uid != euid)
+        setreuid (euid, uid);
+      if (gid != egid)
+        setregid (egid, gid);
+
+      result = access (file, mode);
+      saved_errno = errno;
+
+      /* Restore them.  */
+      if (uid != euid)
+        setreuid (uid, euid);
+      if (gid != egid)
+        setregid (gid, egid);
+
+      errno = saved_errno;
+      return result;
+    }
+
+# else
+
+  /* The following code assumes the traditional Unix model, and is not
+     correct on systems that have ACLs or the like.  However, it's
+     better than nothing, and it is reentrant.  */
+
+  unsigned int granted;
+  if (uid == euid && gid == egid)
+    /* If we are not set-uid or set-gid, access does the same.  */
+    return access (file, mode);
+
+  if (stat (file, &stats) != 0)
+    return -1;
+
+  /* The super-user can read and write any file, and execute any file
+     that anyone can execute.  */
+  if (euid == ROOT_UID
+      && ((mode & X_OK) == 0
+          || (stats.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))))
+    return 0;
+
+  /* Convert the mode to traditional form, clearing any bogus bits.  */
+  if (R_OK == 4 && W_OK == 2 && X_OK == 1 && F_OK == 0)
+    mode &= 7;
+  else
+    mode = ((mode & R_OK ? 4 : 0)
+            + (mode & W_OK ? 2 : 0)
+            + (mode & X_OK ? 1 : 0));
+
+  if (mode == 0)
+    return 0;                   /* The file exists.  */
+
+  /* Convert the file's permission bits to traditional form.  */
+  if (S_IRUSR == (4 << 6) && S_IWUSR == (2 << 6) && S_IXUSR == (1 << 6)
+      && S_IRGRP == (4 << 3) && S_IWGRP == (2 << 3) && S_IXGRP == (1 << 3)
+      && S_IROTH == (4 << 0) && S_IWOTH == (2 << 0) && S_IXOTH == (1 << 0))
+    granted = stats.st_mode;
+  else
+    granted = ((stats.st_mode & S_IRUSR ? 4 << 6 : 0)
+               + (stats.st_mode & S_IWUSR ? 2 << 6 : 0)
+               + (stats.st_mode & S_IXUSR ? 1 << 6 : 0)
+               + (stats.st_mode & S_IRGRP ? 4 << 3 : 0)
+               + (stats.st_mode & S_IWGRP ? 2 << 3 : 0)
+               + (stats.st_mode & S_IXGRP ? 1 << 3 : 0)
+               + (stats.st_mode & S_IROTH ? 4 << 0 : 0)
+               + (stats.st_mode & S_IWOTH ? 2 << 0 : 0)
+               + (stats.st_mode & S_IXOTH ? 1 << 0 : 0));
+
+  if (euid == stats.st_uid)
+    granted >>= 6;
+  else if (egid == stats.st_gid || group_member (stats.st_gid))
+    granted >>= 3;
+
+  if ((mode & ~granted) == 0)
+    return 0;
+  __set_errno (EACCESS);
+  return -1;
+
+# endif
+#endif
+}
+#undef euidaccess
+#ifdef weak_alias
+weak_alias (__euidaccess, euidaccess)
+#endif
+\f
+#ifdef TEST
+# include <error.h>
+# include <stdio.h>
+# include <stdlib.h>
+
+char *program_name;
+
+int
+main (int argc, char **argv)
+{
+  char *file;
+  int mode;
+  int err;
+
+  program_name = argv[0];
+  if (argc < 3)
+    abort ();
+  file = argv[1];
+  mode = atoi (argv[2]);
+
+  err = euidaccess (file, mode);
+  printf ("%d\n", err);
+  if (err != 0)
+    error (0, errno, "%s", file);
+  exit (0);
+}
+#endif

=== added file 'lib/getgroups.c'
--- lib/getgroups.c	1970-01-01 00:00:00 +0000
+++ lib/getgroups.c	2012-10-12 23:28:31 +0000
@@ -0,0 +1,116 @@
+/* provide consistent interface to getgroups for systems that don't allow N==0
+
+   Copyright (C) 1996, 1999, 2003, 2006-2012 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* written by Jim Meyering */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#if !HAVE_GETGROUPS
+
+/* Provide a stub that fails with ENOSYS, since there is no group
+   information available on mingw.  */
+int
+getgroups (int n _GL_UNUSED, GETGROUPS_T *groups _GL_UNUSED)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+#else /* HAVE_GETGROUPS */
+
+# undef getgroups
+# ifndef GETGROUPS_ZERO_BUG
+#  define GETGROUPS_ZERO_BUG 0
+# endif
+
+/* On at least Ultrix 4.3 and NextStep 3.2, getgroups (0, NULL) always
+   fails.  On other systems, it returns the number of supplemental
+   groups for the process.  This function handles that special case
+   and lets the system-provided function handle all others.  However,
+   it can fail with ENOMEM if memory is tight.  It is unspecified
+   whether the effective group id is included in the list.  */
+
+int
+rpl_getgroups (int n, gid_t *group)
+{
+  int n_groups;
+  GETGROUPS_T *gbuf;
+  int saved_errno;
+
+  if (n < 0)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  if (n != 0 || !GETGROUPS_ZERO_BUG)
+    {
+      int result;
+      if (sizeof *group == sizeof *gbuf)
+        return getgroups (n, (GETGROUPS_T *) group);
+
+      if (SIZE_MAX / sizeof *gbuf <= n)
+        {
+          errno = ENOMEM;
+          return -1;
+        }
+      gbuf = malloc (n * sizeof *gbuf);
+      if (!gbuf)
+        return -1;
+      result = getgroups (n, gbuf);
+      if (0 <= result)
+        {
+          n = result;
+          while (n--)
+            group[n] = gbuf[n];
+        }
+      saved_errno = errno;
+      free (gbuf);
+      errno == saved_errno;
+      return result;
+    }
+
+  n = 20;
+  while (1)
+    {
+      /* No need to worry about address arithmetic overflow here,
+         since the ancient systems that we're running on have low
+         limits on the number of secondary groups.  */
+      gbuf = malloc (n * sizeof *gbuf);
+      if (!gbuf)
+        return -1;
+      n_groups = getgroups (n, gbuf);
+      if (n_groups == -1 ? errno != EINVAL : n_groups < n)
+        break;
+      free (gbuf);
+      n *= 2;
+    }
+
+  saved_errno = errno;
+  free (gbuf);
+  errno = saved_errno;
+
+  return n_groups;
+}
+
+#endif /* HAVE_GETGROUPS */

=== modified file 'lib/gnulib.mk'
--- lib/gnulib.mk	2012-10-04 07:15:42 +0000
+++ lib/gnulib.mk	2012-10-12 23:28:31 +0000
@@ -21,7 +21,7 @@
 # the same distribution terms as the rest of that program.
 #
 # Generated by gnulib-tool.
-# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=errno --avoid=fcntl --avoid=fcntl-h --avoid=fstat --avoid=msvc-inval --avoid=msvc-nothrow --avoid=raise --avoid=select --avoid=sigprocmask --avoid=sys_types --avoid=threadlib --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt c-ctype c-strcase careadlinkat crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo filemode getloadavg getopt-gnu gettime gettimeofday ignore-value intprops largefile lstat manywarnings mktime pselect pthread_sigmask readlink socklen stat-time stdalign stdarg stdbool stdio strftime strtoimax strtoumax symlink sys_stat sys_time time timer-time timespec-add timespec-sub utimens warnings
+# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=errno --avoid=fcntl --avoid=fcntl-h --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=raise --avoid=select --avoid=sigprocmask --avoid=sys_types --avoid=threadlib --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt c-ctype c-strcase careadlinkat crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ euidaccess execinfo filemode getloadavg getopt-gnu gettime gettimeofday ignore-value intprops largefile lstat manywarnings mktime pselect pthread_sigmask readlink socklen stat-time stdalign stdarg stdbool stdio strftime strtoimax strtoumax symlink sys_stat sys_time time timer-time timespec-add timespec-sub utimens warnings
 
 
 MOSTLYCLEANFILES += core *.stackdump
@@ -150,6 +150,15 @@
 
 ## end   gnulib module dup2
 
+## begin gnulib module euidaccess
+
+
+EXTRA_DIST += euidaccess.c
+
+EXTRA_libgnu_a_SOURCES += euidaccess.c
+
+## end   gnulib module euidaccess
+
 ## begin gnulib module execinfo
 
 BUILT_SOURCES += $(EXECINFO_H)
@@ -183,6 +192,17 @@
 
 ## end   gnulib module filemode
 
+## begin gnulib module getgroups
+
+if gl_GNULIB_ENABLED_getgroups
+
+endif
+EXTRA_DIST += getgroups.c
+
+EXTRA_libgnu_a_SOURCES += getgroups.c
+
+## end   gnulib module getgroups
+
 ## begin gnulib module getloadavg
 
 
@@ -242,6 +262,17 @@
 
 ## end   gnulib module gettimeofday
 
+## begin gnulib module group-member
+
+if gl_GNULIB_ENABLED_a9786850e999ae65a836a6041e8e5ed1
+
+endif
+EXTRA_DIST += group-member.c
+
+EXTRA_libgnu_a_SOURCES += group-member.c
+
+## end   gnulib module group-member
+
 ## begin gnulib module ignore-value
 
 
@@ -354,6 +385,13 @@
 
 ## end   gnulib module readlink
 
+## begin gnulib module root-uid
+
+
+EXTRA_DIST += root-uid.h
+
+## end   gnulib module root-uid
+
 ## begin gnulib module signal-h
 
 BUILT_SOURCES += signal.h
@@ -1312,6 +1350,15 @@
 
 ## end   gnulib module verify
 
+## begin gnulib module xalloc-oversized
+
+if gl_GNULIB_ENABLED_682e609604ccaac6be382e4ee3a4eaec
+
+endif
+EXTRA_DIST += xalloc-oversized.h
+
+## end   gnulib module xalloc-oversized
+
 
 mostlyclean-local: mostlyclean-generic
 	@for dir in '' $(MOSTLYCLEANDIRS); do \

=== added file 'lib/group-member.c'
--- lib/group-member.c	1970-01-01 00:00:00 +0000
+++ lib/group-member.c	2012-10-12 23:28:31 +0000
@@ -0,0 +1,119 @@
+/* group-member.c -- determine whether group id is in calling user's group list
+
+   Copyright (C) 1994, 1997-1998, 2003, 2005-2006, 2009-2012 Free Software
+   Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <unistd.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "xalloc-oversized.h"
+
+/* Most processes have no more than this many groups, and for these
+   processes we can avoid using malloc.  */
+enum { GROUPBUF_SIZE = 100 };
+
+struct group_info
+  {
+    gid_t *group;
+    gid_t groupbuf[GROUPBUF_SIZE];
+  };
+
+static void
+free_group_info (struct group_info const *g)
+{
+  if (g->group != g->groupbuf)
+    free (g->group);
+}
+
+static int
+get_group_info (struct group_info *gi)
+{
+  int n_groups = getgroups (GROUPBUF_SIZE, gi->groupbuf);
+  gi->group = gi->groupbuf;
+
+  if (n_groups < 0)
+    {
+      int n_group_slots = getgroups (0, NULL);
+      if (0 <= n_group_slots
+          && ! xalloc_oversized (n_group_slots, sizeof *gi->group))
+        {
+          gi->group = malloc (n_group_slots * sizeof *gi->group);
+          if (gi->group)
+            n_groups = getgroups (n_group_slots, gi->group);
+        }
+    }
+
+  /* In case of error, the user loses.  */
+  return n_groups;
+}
+
+/* Return non-zero if GID is one that we have in our groups list.
+   Note that the groups list is not guaranteed to contain the current
+   or effective group ID, so they should generally be checked
+   separately.  */
+
+int
+group_member (gid_t gid)
+{
+  int i;
+  int found;
+  struct group_info gi;
+  int n_groups = get_group_info (&gi);
+
+  /* Search through the list looking for GID. */
+  found = 0;
+  for (i = 0; i < n_groups; i++)
+    {
+      if (gid == gi.group[i])
+        {
+          found = 1;
+          break;
+        }
+    }
+
+  free_group_info (&gi);
+
+  return found;
+}
+
+#ifdef TEST
+
+char *program_name;
+
+int
+main (int argc, char **argv)
+{
+  int i;
+
+  program_name = argv[0];
+
+  for (i = 1; i < argc; i++)
+    {
+      gid_t gid;
+
+      gid = atoi (argv[i]);
+      printf ("%d: %s\n", gid, group_member (gid) ? "yes" : "no");
+    }
+  exit (0);
+}
+
+#endif /* TEST */

=== added file 'lib/root-uid.h'
--- lib/root-uid.h	1970-01-01 00:00:00 +0000
+++ lib/root-uid.h	2012-10-12 23:28:31 +0000
@@ -0,0 +1,30 @@
+/* The user ID that always has appropriate privileges in the POSIX sense.
+
+   Copyright 2012 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+   Written by Paul Eggert.  */
+
+#ifndef ROOT_UID_H_
+#define ROOT_UID_H_
+
+/* The user ID that always has appropriate privileges in the POSIX sense.  */
+#ifdef __TANDEM
+# define ROOT_UID 65535
+#else
+# define ROOT_UID 0
+#endif
+
+#endif

=== added file 'lib/xalloc-oversized.h'
--- lib/xalloc-oversized.h	1970-01-01 00:00:00 +0000
+++ lib/xalloc-oversized.h	2012-10-12 23:28:31 +0000
@@ -0,0 +1,38 @@
+/* xalloc-oversized.h -- memory allocation size checking
+
+   Copyright (C) 1990-2000, 2003-2004, 2006-2012 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef XALLOC_OVERSIZED_H_
+# define XALLOC_OVERSIZED_H_
+
+# include <stddef.h>
+
+/* Return 1 if an array of N objects, each of size S, cannot exist due
+   to size arithmetic overflow.  S must be positive and N must be
+   nonnegative.  This is a macro, not a function, so that it
+   works correctly even when SIZE_MAX < N.
+
+   By gnulib convention, SIZE_MAX represents overflow in size
+   calculations, so the conservative dividend to use here is
+   SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value.
+   However, malloc (SIZE_MAX) fails on all known hosts where
+   sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for
+   exactly-SIZE_MAX allocations on such hosts; this avoids a test and
+   branch when S is known to be 1.  */
+# define xalloc_oversized(n, s) \
+    ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n))
+
+#endif /* !XALLOC_OVERSIZED_H_ */

=== added file 'm4/euidaccess.m4'
--- m4/euidaccess.m4	1970-01-01 00:00:00 +0000
+++ m4/euidaccess.m4	2012-10-12 23:28:31 +0000
@@ -0,0 +1,52 @@
+# euidaccess.m4 serial 14
+dnl Copyright (C) 2002-2012 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_NONREENTRANT_EUIDACCESS],
+[
+  AC_REQUIRE([gl_FUNC_EUIDACCESS])
+  AC_DEFINE([PREFER_NONREENTRANT_EUIDACCESS], [1],
+    [Define this if you prefer euidaccess to return the correct result
+     even if this would make it nonreentrant.  Define this only if your
+     entire application is safe even if the uid or gid might temporarily
+     change.  If your application uses signal handlers or threads it
+     is probably not safe.])
+])
+
+AC_DEFUN([gl_FUNC_EUIDACCESS],
+[
+  AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+
+  dnl Persuade glibc <unistd.h> to declare euidaccess().
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_FUNCS([euidaccess])
+  if test $ac_cv_func_euidaccess = no; then
+    HAVE_EUIDACCESS=0
+  fi
+])
+
+# Prerequisites of lib/euidaccess.c.
+AC_DEFUN([gl_PREREQ_EUIDACCESS], [
+  dnl Prefer POSIX faccessat over non-standard euidaccess.
+  AC_CHECK_FUNCS_ONCE([faccessat])
+  dnl Try various other non-standard fallbacks.
+  AC_CHECK_HEADERS_ONCE([libgen.h])
+  AC_CHECK_DECLS_ONCE([setregid])
+  AC_REQUIRE([AC_FUNC_GETGROUPS])
+
+  # Solaris 9 and 10 need -lgen to get the eaccess function.
+  # Save and restore LIBS so -lgen isn't added to it.  Otherwise, *all*
+  # programs in the package would end up linked with that potentially-shared
+  # library, inducing unnecessary run-time overhead.
+  LIB_EACCESS=
+  AC_SUBST([LIB_EACCESS])
+  gl_saved_libs=$LIBS
+    AC_SEARCH_LIBS([eaccess], [gen],
+                   [test "$ac_cv_search_eaccess" = "none required" ||
+                    LIB_EACCESS=$ac_cv_search_eaccess])
+    AC_CHECK_FUNCS([eaccess])
+  LIBS=$gl_saved_libs
+])

=== added file 'm4/getgroups.m4'
--- m4/getgroups.m4	1970-01-01 00:00:00 +0000
+++ m4/getgroups.m4	2012-10-12 23:28:31 +0000
@@ -0,0 +1,107 @@
+# serial 18
+
+dnl From Jim Meyering.
+dnl A wrapper around AC_FUNC_GETGROUPS.
+
+# Copyright (C) 1996-1997, 1999-2004, 2008-2012 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+m4_version_prereq([2.70], [] ,[
+
+# This is taken from the following Autoconf patch:
+# http://git.savannah.gnu.org/gitweb/?p=autoconf.git;a=commitdiff;h=7fbb553727ed7e0e689a17594b58559ecf3ea6e9
+AC_DEFUN([AC_FUNC_GETGROUPS],
+[
+  AC_REQUIRE([AC_TYPE_GETGROUPS])dnl
+  AC_REQUIRE([AC_TYPE_SIZE_T])dnl
+  AC_REQUIRE([AC_CANONICAL_HOST])dnl for cross-compiles
+  AC_CHECK_FUNC([getgroups])
+
+  # If we don't yet have getgroups, see if it's in -lbsd.
+  # This is reported to be necessary on an ITOS 3000WS running SEIUX 3.1.
+  ac_save_LIBS=$LIBS
+  if test $ac_cv_func_getgroups = no; then
+    AC_CHECK_LIB(bsd, getgroups, [GETGROUPS_LIB=-lbsd])
+  fi
+
+  # Run the program to test the functionality of the system-supplied
+  # getgroups function only if there is such a function.
+  if test $ac_cv_func_getgroups = yes; then
+    AC_CACHE_CHECK([for working getgroups], [ac_cv_func_getgroups_works],
+      [AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+            [AC_INCLUDES_DEFAULT],
+            [[/* On Ultrix 4.3, getgroups (0, 0) always fails.  */
+              return getgroups (0, 0) == -1;]])
+         ],
+         [ac_cv_func_getgroups_works=yes],
+         [ac_cv_func_getgroups_works=no],
+         [case "$host_os" in # ((
+                    # Guess yes on glibc systems.
+            *-gnu*) ac_cv_func_getgroups_works="guessing yes" ;;
+                    # If we don't know, assume the worst.
+            *)      ac_cv_func_getgroups_works="guessing no" ;;
+          esac
+         ])
+      ])
+  else
+    ac_cv_func_getgroups_works=no
+  fi
+  case "$ac_cv_func_getgroups_works" in
+    *yes)
+      AC_DEFINE([HAVE_GETGROUPS], [1],
+        [Define to 1 if your system has a working `getgroups' function.])
+      ;;
+  esac
+  LIBS=$ac_save_LIBS
+])# AC_FUNC_GETGROUPS
+
+])
+
+AC_DEFUN([gl_FUNC_GETGROUPS],
+[
+  AC_REQUIRE([AC_TYPE_GETGROUPS])
+  AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+
+  AC_FUNC_GETGROUPS
+  if test $ac_cv_func_getgroups != yes; then
+    HAVE_GETGROUPS=0
+  else
+    if test "$ac_cv_type_getgroups" != gid_t \
+       || { case "$ac_cv_func_getgroups_works" in
+              *yes) false;;
+              *) true;;
+            esac
+          }; then
+      REPLACE_GETGROUPS=1
+      AC_DEFINE([GETGROUPS_ZERO_BUG], [1], [Define this to 1 if
+        getgroups(0,NULL) does not return the number of groups.])
+    else
+      dnl Detect FreeBSD bug; POSIX requires getgroups(-1,ptr) to fail.
+      AC_CACHE_CHECK([whether getgroups handles negative values],
+        [gl_cv_func_getgroups_works],
+        [AC_RUN_IFELSE([AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],
+          [[int size = getgroups (0, 0);
+            gid_t *list = malloc (size * sizeof *list);
+            return getgroups (-1, list) != -1;]])],
+          [gl_cv_func_getgroups_works=yes],
+          [gl_cv_func_getgroups_works=no],
+          [case "$host_os" in
+                     # Guess yes on glibc systems.
+             *-gnu*) gl_cv_func_getgroups_works="guessing yes" ;;
+                     # If we don't know, assume the worst.
+             *)      gl_cv_func_getgroups_works="guessing no" ;;
+           esac
+          ])])
+      case "$gl_cv_func_getgroups_works" in
+        *yes) ;;
+        *) REPLACE_GETGROUPS=1 ;;
+      esac
+    fi
+  fi
+  test -n "$GETGROUPS_LIB" && LIBS="$GETGROUPS_LIB $LIBS"
+])

=== modified file 'm4/gnulib-comp.m4'
--- m4/gnulib-comp.m4	2012-09-27 01:06:23 +0000
+++ m4/gnulib-comp.m4	2012-10-12 23:28:31 +0000
@@ -53,17 +53,20 @@
   # Code from module dtotimespec:
   # Code from module dup2:
   # Code from module environ:
+  # Code from module euidaccess:
   # Code from module execinfo:
   # Code from module extensions:
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
   # Code from module extern-inline:
   # Code from module filemode:
+  # Code from module getgroups:
   # Code from module getloadavg:
   # Code from module getopt-gnu:
   # Code from module getopt-posix:
   # Code from module gettext-h:
   # Code from module gettime:
   # Code from module gettimeofday:
+  # Code from module group-member:
   # Code from module ignore-value:
   # Code from module include_next:
   # Code from module intprops:
@@ -79,6 +82,7 @@
   # Code from module pselect:
   # Code from module pthread_sigmask:
   # Code from module readlink:
+  # Code from module root-uid:
   # Code from module signal-h:
   # Code from module snippet/_Noreturn:
   # Code from module snippet/arg-nonnull:
@@ -120,6 +124,7 @@
   # Code from module utimens:
   # Code from module verify:
   # Code from module warnings:
+  # Code from module xalloc-oversized:
 ])
 
 # This macro should be invoked from ./configure.ac, in the section
@@ -154,6 +159,12 @@
   gl_UNISTD_MODULE_INDICATOR([dup2])
   gl_ENVIRON
   gl_UNISTD_MODULE_INDICATOR([environ])
+  gl_FUNC_EUIDACCESS
+  if test $HAVE_EUIDACCESS = 0; then
+    AC_LIBOBJ([euidaccess])
+    gl_PREREQ_EUIDACCESS
+  fi
+  gl_UNISTD_MODULE_INDICATOR([euidaccess])
   gl_EXECINFO_H
   AC_REQUIRE([gl_EXTERN_INLINE])
   gl_FILEMODE
@@ -269,18 +280,32 @@
   gl_UNISTD_H
   gl_UTIMENS
   gl_gnulib_enabled_dosname=false
+  gl_gnulib_enabled_getgroups=false
   gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=false
+  gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=false
   gl_gnulib_enabled_pathmax=false
   gl_gnulib_enabled_stat=false
   gl_gnulib_enabled_strtoll=false
   gl_gnulib_enabled_strtoull=false
   gl_gnulib_enabled_verify=false
+  gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec=false
   func_gl_gnulib_m4code_dosname ()
   {
     if ! $gl_gnulib_enabled_dosname; then
       gl_gnulib_enabled_dosname=true
     fi
   }
+  func_gl_gnulib_m4code_getgroups ()
+  {
+    if ! $gl_gnulib_enabled_getgroups; then
+      gl_FUNC_GETGROUPS
+      if test $HAVE_GETGROUPS = 0 || test $REPLACE_GETGROUPS = 1; then
+        AC_LIBOBJ([getgroups])
+      fi
+      gl_UNISTD_MODULE_INDICATOR([getgroups])
+      gl_gnulib_enabled_getgroups=true
+    fi
+  }
   func_gl_gnulib_m4code_be453cec5eecf5731a274f2de7f2db36 ()
   {
     if ! $gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36; then
@@ -289,6 +314,24 @@
       gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=true
     fi
   }
+  func_gl_gnulib_m4code_a9786850e999ae65a836a6041e8e5ed1 ()
+  {
+    if ! $gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1; then
+      gl_FUNC_GROUP_MEMBER
+      if test $HAVE_GROUP_MEMBER = 0; then
+        AC_LIBOBJ([group-member])
+        gl_PREREQ_GROUP_MEMBER
+      fi
+      gl_UNISTD_MODULE_INDICATOR([group-member])
+      gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=true
+      if test $HAVE_GROUP_MEMBER = 0; then
+        func_gl_gnulib_m4code_getgroups
+      fi
+      if test $HAVE_GROUP_MEMBER = 0; then
+        func_gl_gnulib_m4code_682e609604ccaac6be382e4ee3a4eaec
+      fi
+    fi
+  }
   func_gl_gnulib_m4code_pathmax ()
   {
     if ! $gl_gnulib_enabled_pathmax; then
@@ -347,6 +390,18 @@
       gl_gnulib_enabled_verify=true
     fi
   }
+  func_gl_gnulib_m4code_682e609604ccaac6be382e4ee3a4eaec ()
+  {
+    if ! $gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec; then
+      gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec=true
+    fi
+  }
+  if test $HAVE_EUIDACCESS = 0; then
+    func_gl_gnulib_m4code_a9786850e999ae65a836a6041e8e5ed1
+  fi
+  if test $HAVE_EUIDACCESS = 0; then
+    func_gl_gnulib_m4code_stat
+  fi
   if test $REPLACE_GETOPT = 1; then
     func_gl_gnulib_m4code_be453cec5eecf5731a274f2de7f2db36
   fi
@@ -373,12 +428,15 @@
   fi
   m4_pattern_allow([^gl_GNULIB_ENABLED_])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_dosname], [$gl_gnulib_enabled_dosname])
+  AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getgroups])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_be453cec5eecf5731a274f2de7f2db36], [$gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36])
+  AM_CONDITIONAL([gl_GNULIB_ENABLED_a9786850e999ae65a836a6041e8e5ed1], [$gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_pathmax], [$gl_gnulib_enabled_pathmax])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_stat], [$gl_gnulib_enabled_stat])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_strtoll], [$gl_gnulib_enabled_strtoll])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_strtoull], [$gl_gnulib_enabled_strtoull])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_verify], [$gl_gnulib_enabled_verify])
+  AM_CONDITIONAL([gl_GNULIB_ENABLED_682e609604ccaac6be382e4ee3a4eaec], [$gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec])
   # End of code from modules
   m4_ifval(gl_LIBSOURCES_LIST, [
     m4_syscmd([test ! -d ]m4_defn([gl_LIBSOURCES_DIR])[ ||
@@ -538,12 +596,14 @@
   lib/dtoastr.c
   lib/dtotimespec.c
   lib/dup2.c
+  lib/euidaccess.c
   lib/execinfo.c
   lib/execinfo.in.h
   lib/filemode.c
   lib/filemode.h
   lib/ftoastr.c
   lib/ftoastr.h
+  lib/getgroups.c
   lib/getloadavg.c
   lib/getopt.c
   lib/getopt.in.h
@@ -552,6 +612,7 @@
   lib/gettext.h
   lib/gettime.c
   lib/gettimeofday.c
+  lib/group-member.c
   lib/ignore-value.h
   lib/intprops.h
   lib/inttypes.in.h
@@ -564,6 +625,7 @@
   lib/pselect.c
   lib/pthread_sigmask.c
   lib/readlink.c
+  lib/root-uid.h
   lib/sha1.c
   lib/sha1.h
   lib/sha256.c
@@ -605,21 +667,25 @@
   lib/utimens.c
   lib/utimens.h
   lib/verify.h
+  lib/xalloc-oversized.h
   m4/00gnulib.m4
   m4/alloca.m4
   m4/c-strtod.m4
   m4/clock_time.m4
   m4/dup2.m4
   m4/environ.m4
+  m4/euidaccess.m4
   m4/execinfo.m4
   m4/extensions.m4
   m4/extern-inline.m4
   m4/filemode.m4
+  m4/getgroups.m4
   m4/getloadavg.m4
   m4/getopt.m4
   m4/gettime.m4
   m4/gettimeofday.m4
   m4/gnulib-common.m4
+  m4/group-member.m4
   m4/include_next.m4
   m4/inttypes.m4
   m4/largefile.m4

=== added file 'm4/group-member.m4'
--- m4/group-member.m4	1970-01-01 00:00:00 +0000
+++ m4/group-member.m4	2012-10-12 23:28:31 +0000
@@ -0,0 +1,29 @@
+# serial 14
+
+# Copyright (C) 1999-2001, 2003-2007, 2009-2012 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+dnl Written by Jim Meyering
+
+AC_DEFUN([gl_FUNC_GROUP_MEMBER],
+[
+  AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+
+  dnl Persuade glibc <unistd.h> to declare group_member().
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  dnl Do this replacement check manually because I want the hyphen
+  dnl (not the underscore) in the filename.
+  AC_CHECK_FUNC([group_member], , [
+    HAVE_GROUP_MEMBER=0
+  ])
+])
+
+# Prerequisites of lib/group-member.c.
+AC_DEFUN([gl_PREREQ_GROUP_MEMBER],
+[
+  AC_REQUIRE([AC_FUNC_GETGROUPS])
+])

=== modified file 'nt/ChangeLog'
--- nt/ChangeLog	2012-10-08 14:14:22 +0000
+++ nt/ChangeLog	2012-10-14 18:06:01 +0000
@@ -1,3 +1,9 @@
+2012-10-14  Paul Eggert  <eggert@cs.ucla.edu>
+
+	Use euidaccess, not access, when checking file permissions (Bug#12632).
+	* inc/ms-w32.h (euidaccess): Define this, not 'access', since
+	the mainline code now uses euidaccess.
+
 2012-10-08  Juanma Barranquero  <lekktu@gmail.com>
 
 	* config.nt: Sync with autogen/config.in.

=== modified file 'nt/inc/ms-w32.h'
--- nt/inc/ms-w32.h	2012-09-30 21:36:42 +0000
+++ nt/inc/ms-w32.h	2012-10-14 18:06:01 +0000
@@ -160,8 +160,6 @@
 #endif
 
 /* Calls that are emulated or shadowed.  */
-#undef access
-#define access  sys_access
 #undef chdir
 #define chdir   sys_chdir
 #undef chmod
@@ -176,6 +174,7 @@
 #define dup     sys_dup
 #undef dup2
 #define dup2    sys_dup2
+#define euidaccess  sys_access
 #define fopen   sys_fopen
 #define link    sys_link
 #define localtime sys_localtime

=== modified file 'src/ChangeLog'
--- src/ChangeLog	2012-10-14 00:17:07 +0000
+++ src/ChangeLog	2012-10-14 18:06:01 +0000
@@ -1,3 +1,27 @@
+2012-10-14  Paul Eggert  <eggert@cs.ucla.edu>
+
+	Use euidaccess, not access, when checking file permissions (Bug#12632).
+	* Makefile.in (LIB_EACCESS): New macro.
+	(LIBES): Use it.
+	* callproc.c (init_callproc):
+	* charset.c (init_charset):
+	* fileio.c (check_existing, check_executable):
+	* lread.c (openp, load_path_check):
+	* process.c (allocate_pty):
+	* xrdb.c (file_p):
+	Use euidaccess, not access.  Use symbolic names instead of integers
+	for the flags, as they're portable now.
+	* fileio.c (Ffile_readable_p):
+	Use euidaccess, not stat + open + close.
+	(file_directory_p): New function, which uses 'stat' on most places
+	but 'access' (for efficiency) if WINDOWSNT.
+	(Ffile_directory_p): Use it.
+	* lisp.h (file_directory_p): New decl.
+	* lread.c (openp): When opening a file, use fstat rather than
+	stat, as that avoids a permissions race.  When not opening a file,
+	use file_directory_p rather than stat.
+	* msdos.c (init_environment, readlink): Use sys_access, not access.
+
 2012-10-13  Jan Djärv  <jan.h.d@swipnet.se>
 
 	* gtkutil.c (xg_set_widget_bg): Divide by 65535 (Bug#12612).

=== modified file 'src/Makefile.in'
--- src/Makefile.in	2012-10-08 12:53:18 +0000
+++ src/Makefile.in	2012-10-13 01:49:41 +0000
@@ -150,6 +150,7 @@
 M17N_FLT_LIBS = @M17N_FLT_LIBS@
 
 LIB_CLOCK_GETTIME=@LIB_CLOCK_GETTIME@
+LIB_EACCESS=@LIB_EACCESS@
 LIB_TIMER_TIME=@LIB_TIMER_TIME@
 
 DBUS_CFLAGS = @DBUS_CFLAGS@
@@ -392,7 +393,7 @@
 LIBES = $(LIBS) $(W32_LIBS) $(LIBX_BASE) $(LIBIMAGE) \
    $(LIBX_OTHER) $(LIBSOUND) \
    $(RSVG_LIBS) $(IMAGEMAGICK_LIBS) $(LIB_CLOCK_GETTIME) \
-   $(LIB_TIMER_TIME) $(DBUS_LIBS) \
+   $(LIB_EACCESS) $(LIB_TIMER_TIME) $(DBUS_LIBS) \
    $(LIB_EXECINFO) \
    $(LIBXML2_LIBS) $(LIBGPM) $(LIBRESOLV) $(LIBS_SYSTEM) \
    $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \

=== modified file 'src/callproc.c'
--- src/callproc.c	2012-09-23 22:25:22 +0000
+++ src/callproc.c	2012-10-12 23:28:31 +0000
@@ -1597,13 +1597,13 @@
 #endif
     {
       tempdir = Fdirectory_file_name (Vexec_directory);
-      if (access (SSDATA (tempdir), 0) < 0)
+      if (euidaccess (SSDATA (tempdir), F_OK) < 0)
 	dir_warning ("Warning: arch-dependent data dir (%s) does not exist.\n",
 		     Vexec_directory);
     }
 
   tempdir = Fdirectory_file_name (Vdata_directory);
-  if (access (SSDATA (tempdir), 0) < 0)
+  if (euidaccess (SSDATA (tempdir), F_OK) < 0)
     dir_warning ("Warning: arch-independent data dir (%s) does not exist.\n",
 		 Vdata_directory);
 

=== modified file 'src/charset.c'
--- src/charset.c	2012-10-01 06:36:54 +0000
+++ src/charset.c	2012-10-12 23:28:31 +0000
@@ -2293,7 +2293,7 @@
 {
   Lisp_Object tempdir;
   tempdir = Fexpand_file_name (build_string ("charsets"), Vdata_directory);
-  if (access (SSDATA (tempdir), 0) < 0)
+  if (euidaccess (SSDATA (tempdir), F_OK) != 0)
     {
       /* This used to be non-fatal (dir_warning), but it should not
          happen, and if it does sooner or later it will cause some

=== modified file 'src/fileio.c'
--- src/fileio.c	2012-10-13 08:55:26 +0000
+++ src/fileio.c	2012-10-14 18:06:01 +0000
@@ -2424,15 +2424,7 @@
 bool
 check_existing (const char *filename)
 {
-#ifdef DOS_NT
-  /* The full emulation of Posix 'stat' is too expensive on
-     DOS/Windows, when all we want to know is whether the file exists.
-     So we use 'access' instead, which is much more lightweight.  */
-  return (access (filename, F_OK) >= 0);
-#else
-  struct stat st;
-  return (stat (filename, &st) >= 0);
-#endif
+  return euidaccess (filename, F_OK) == 0;
 }
 
 /* Return true if file FILENAME exists and can be executed.  */
@@ -2440,21 +2432,7 @@
 static bool
 check_executable (char *filename)
 {
-#ifdef DOS_NT
-  struct stat st;
-  if (stat (filename, &st) < 0)
-    return 0;
-  return ((st.st_mode & S_IEXEC) != 0);
-#else /* not DOS_NT */
-#ifdef HAVE_EUIDACCESS
-  return (euidaccess (filename, 1) >= 0);
-#else
-  /* Access isn't quite right because it uses the real uid
-     and we really want to test with the effective uid.
-     But Unix doesn't give us a right way to do it.  */
-  return (access (filename, 1) >= 0);
-#endif
-#endif /* not DOS_NT */
+  return euidaccess (filename, X_OK) == 0;
 }
 
 /* Return true if file FILENAME exists and can be written.  */
@@ -2463,13 +2441,14 @@
 check_writable (const char *filename)
 {
 #ifdef MSDOS
+  /* FIXME: an euidaccess implementation should be added to the
+     DOS/Windows ports and this #ifdef branch should be removed.  */
   struct stat st;
   if (stat (filename, &st) < 0)
     return 0;
   return (st.st_mode & S_IWRITE || S_ISDIR (st.st_mode));
 #else /* not MSDOS */
-#ifdef HAVE_EUIDACCESS
-  bool res = (euidaccess (filename, 2) >= 0);
+  bool res = euidaccess (filename, W_OK) == 0;
 #ifdef CYGWIN
   /* euidaccess may have returned failure because Cygwin couldn't
      determine the file's UID or GID; if so, we return success. */
@@ -2482,14 +2461,6 @@
     }
 #endif /* CYGWIN */
   return res;
-#else /* not HAVE_EUIDACCESS */
-  /* Access isn't quite right because it uses the real uid
-     and we really want to test with the effective uid.
-     But Unix doesn't give us a right way to do it.
-     Opening with O_WRONLY could work for an ordinary file,
-     but would lose for directories.  */
-  return (access (filename, 2) >= 0);
-#endif /* not HAVE_EUIDACCESS */
 #endif /* not MSDOS */
 }
 
@@ -2546,9 +2517,6 @@
 {
   Lisp_Object absname;
   Lisp_Object handler;
-  int desc;
-  int flags;
-  struct stat statbuf;
 
   CHECK_STRING (filename);
   absname = Fexpand_file_name (filename, Qnil);
@@ -2560,31 +2528,7 @@
     return call2 (handler, Qfile_readable_p, absname);
 
   absname = ENCODE_FILE (absname);
-
-#if defined (DOS_NT) || defined (macintosh)
-  /* Under MS-DOS, Windows, and Macintosh, open does not work for
-     directories.  */
-  if (access (SDATA (absname), 0) == 0)
-    return Qt;
-  return Qnil;
-#else /* not DOS_NT and not macintosh */
-  flags = O_RDONLY;
-#ifdef O_NONBLOCK
-  /* Opening a fifo without O_NONBLOCK can wait.
-     We don't want to wait.  But we don't want to mess wth O_NONBLOCK
-     except in the case of a fifo, on a system which handles it.  */
-  desc = stat (SSDATA (absname), &statbuf);
-  if (desc < 0)
-    return Qnil;
-  if (S_ISFIFO (statbuf.st_mode))
-    flags |= O_NONBLOCK;
-#endif
-  desc = emacs_open (SSDATA (absname), flags, 0);
-  if (desc < 0)
-    return Qnil;
-  emacs_close (desc);
-  return Qt;
-#endif /* not DOS_NT and not macintosh */
+  return euidaccess (SSDATA (absname), R_OK) == 0 ? Qt : Qnil;
 }
 
 /* Having this before file-symlink-p mysteriously caused it to be forgotten
@@ -2621,7 +2565,7 @@
   /* The read-only attribute of the parent directory doesn't affect
      whether a file or directory can be created within it.  Some day we
      should check ACLs though, which do affect this.  */
-  return (access (SDATA (dir), D_OK) < 0) ? Qnil : Qt;
+  return file_directory_p (SDATA (dir)) ? Qt : Qnil;
 #else
   return (check_writable (!NILP (dir) ? SSDATA (dir) : "")
 	  ? Qt : Qnil);
@@ -2702,8 +2646,7 @@
 See `file-symlink-p' to distinguish symlinks.  */)
   (Lisp_Object filename)
 {
-  register Lisp_Object absname;
-  struct stat st;
+  Lisp_Object absname;
   Lisp_Object handler;
 
   absname = expand_and_dir_to_file (filename, BVAR (current_buffer, directory));
@@ -2716,9 +2659,20 @@
 
   absname = ENCODE_FILE (absname);
 
-  if (stat (SSDATA (absname), &st) < 0)
-    return Qnil;
-  return S_ISDIR (st.st_mode) ? Qt : Qnil;
+  return file_directory_p (SSDATA (absname)) ? Qt : Qnil;
+}
+
+/* Return true if FILE is a directory or a symlink to a directory.  */
+bool
+file_directory_p (char const *file)
+{
+#ifdef WINDOWSNT
+  /* This is cheaper than 'stat'.  */
+  return euidaccess (file, D_OK) == 0;
+#else
+  struct stat st;
+  return stat (file, &st) == 0 && S_ISDIR (st.st_mode);
+#endif
 }
 
 DEFUN ("file-accessible-directory-p", Ffile_accessible_directory_p,

=== modified file 'src/lisp.h'
--- src/lisp.h	2012-10-12 15:19:54 +0000
+++ src/lisp.h	2012-10-14 06:00:40 +0000
@@ -3179,6 +3179,7 @@
 extern Lisp_Object restore_point_unwind (Lisp_Object);
 extern _Noreturn void report_file_error (const char *, Lisp_Object);
 extern void internal_delete_file (Lisp_Object);
+extern bool file_directory_p (const char *);
 extern void syms_of_fileio (void);
 extern Lisp_Object make_temp_name (Lisp_Object, bool);
 extern Lisp_Object Qdelete_file;

=== modified file 'src/lread.c'
--- src/lread.c	2012-10-12 15:19:54 +0000
+++ src/lread.c	2012-10-14 06:00:40 +0000
@@ -1404,7 +1404,7 @@
 If SUFFIXES is non-nil, it should be a list of suffixes to append to
 file name when searching.
 If non-nil, PREDICATE is used instead of `file-readable-p'.
-PREDICATE can also be an integer to pass to the access(2) function,
+PREDICATE can also be an integer to pass to the euidaccess(2) function,
 in which case file-name-handlers are ignored.
 This function will normally skip directories, so if you want it to find
 directories, make sure the PREDICATE function returns `dir-ok' for them.  */)
@@ -1442,7 +1442,6 @@
 int
 openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, Lisp_Object *storeptr, Lisp_Object predicate)
 {
-  int fd;
   ptrdiff_t fn_size = 100;
   char buf[100];
   char *fn = buf;
@@ -1497,7 +1496,6 @@
 	{
 	  ptrdiff_t fnlen, lsuffix = SBYTES (XCAR (tail));
 	  Lisp_Object handler;
-	  bool exists;
 
 	  /* Concatenate path element/specified name with the suffix.
 	     If the directory starts with /:, remove that.  */
@@ -1521,6 +1519,7 @@
 	  handler = Ffind_file_name_handler (string, Qfile_exists_p);
 	  if ((!NILP (handler) || !NILP (predicate)) && !NATNUMP (predicate))
             {
+	      bool exists;
 	      if (NILP (predicate))
 		exists = !NILP (Ffile_readable_p (string));
 	      else
@@ -1542,37 +1541,38 @@
 	    }
 	  else
 	    {
-#ifndef WINDOWSNT
-	      struct stat st;
-#endif
+	      int fd;
 	      const char *pfn;
 
 	      encoded_fn = ENCODE_FILE (string);
 	      pfn = SSDATA (encoded_fn);
-#ifdef WINDOWSNT
-	      exists = access (pfn, F_OK) == 0 && access (pfn, D_OK) < 0;
-#else
-	      exists = (stat (pfn, &st) == 0 && ! S_ISDIR (st.st_mode));
-#endif
-	      if (exists)
+
+	      /* Check that we can access or open it.  */
+	      if (NATNUMP (predicate))
+		fd = (((XFASTINT (predicate) & ~INT_MAX) == 0
+		       && euidaccess (pfn, XFASTINT (predicate)) == 0
+		       && ! file_directory_p (pfn))
+		      ? 1 : -1);
+	      else
 		{
-		  /* Check that we can access or open it.  */
-		  if (NATNUMP (predicate))
-		    fd = (((XFASTINT (predicate) & ~INT_MAX) == 0
-			   && access (pfn, XFASTINT (predicate)) == 0)
-			  ? 1 : -1);
-		  else
-		    fd = emacs_open (pfn, O_RDONLY, 0);
-
-		  if (fd >= 0)
+		  struct stat st;
+		  fd = emacs_open (pfn, O_RDONLY, 0);
+		  if (0 <= fd
+		      && (fstat (fd, &st) != 0 || S_ISDIR (st.st_mode)))
 		    {
-		      /* We succeeded; return this descriptor and filename.  */
-		      if (storeptr)
-			*storeptr = string;
-		      UNGCPRO;
-		      return fd;
+		      emacs_close (fd);
+		      fd = -1;
 		    }
 		}
+
+	      if (fd >= 0)
+		{
+		  /* We succeeded; return this descriptor and filename.  */
+		  if (storeptr)
+		    *storeptr = string;
+		  UNGCPRO;
+		  return fd;
+		}
 	    }
 	}
       if (absolute)
@@ -4088,7 +4088,7 @@
       if (STRINGP (dirfile))
         {
           dirfile = Fdirectory_file_name (dirfile);
-          if (access (SSDATA (dirfile), 0) < 0)
+          if (euidaccess (SSDATA (dirfile), F_OK) != 0)
             dir_warning ("Warning: Lisp directory `%s' does not exist.\n",
                          XCAR (path_tail));
         }

=== modified file 'src/msdos.c'
--- src/msdos.c	2012-09-23 08:44:20 +0000
+++ src/msdos.c	2012-10-14 18:06:01 +0000
@@ -3557,7 +3557,7 @@
 	 read-only filesystem, like CD-ROM or a write-protected floppy.
 	 The only way to be really sure is to actually create a file and
 	 see if it succeeds.  But I think that's too much to ask.  */
-      if (tmp && access (tmp, D_OK) == 0)
+      if (tmp && sys_access (tmp, D_OK) == 0)
 	{
 	  setenv ("TMPDIR", tmp, 1);
 	  break;
@@ -3935,7 +3935,7 @@
 readlink (const char *name, char *dummy1, size_t dummy2)
 {
   /* `access' is much faster than `stat' on MS-DOS.  */
-  if (access (name, F_OK) == 0)
+  if (sys_access (name, F_OK) == 0)
     errno = EINVAL;
   return -1;
 }

=== modified file 'src/process.c'
--- src/process.c	2012-10-13 00:52:01 +0000
+++ src/process.c	2012-10-13 00:55:20 +0000
@@ -663,7 +663,7 @@
 #else
 	    sprintf (pty_name, "/dev/tty%c%x", c, i);
 #endif /* no PTY_TTY_NAME_SPRINTF */
-	    if (access (pty_name, 6) != 0)
+	    if (euidaccess (pty_name, R_OK | W_OK) != 0)
 	      {
 		emacs_close (fd);
 # ifndef __sgi

=== modified file 'src/xrdb.c'
--- src/xrdb.c	2012-09-15 07:06:56 +0000
+++ src/xrdb.c	2012-10-12 23:28:31 +0000
@@ -268,7 +268,7 @@
 {
   struct stat status;
 
-  return (access (filename, 4) == 0             /* exists and is readable */
+  return (euidaccess (filename, R_OK) == 0      /* exists and is readable */
 	  && stat (filename, &status) == 0	/* get the status */
 	  && (S_ISDIR (status.st_mode)) == 0);	/* not a directory */
 }


  reply	other threads:[~2012-10-14 18:14 UTC|newest]

Thread overview: 47+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-10-13  1:58 bug#12632: file permissions checking mishandled when setuid Paul Eggert
2012-10-13  7:23 ` Eli Zaretskii
2012-10-13  8:36   ` Eli Zaretskii
2012-10-14  6:16     ` Paul Eggert
2012-10-14  6:56       ` Eli Zaretskii
2012-10-14 18:14         ` Paul Eggert [this message]
2012-10-14 18:39           ` Eli Zaretskii
2012-10-14 19:42             ` Paul Eggert
2012-10-14 20:10               ` Eli Zaretskii
2012-10-14 20:17               ` Eli Zaretskii
2012-10-14 20:40                 ` Paul Eggert
2012-10-14 20:53                   ` Eli Zaretskii
2012-10-15  6:17                     ` Paul Eggert
2012-10-15 17:31                       ` Eli Zaretskii
2012-10-15 21:38                         ` Paul Eggert
2012-10-16  3:46                           ` Eli Zaretskii
2012-10-16  6:00                             ` Paul Eggert
2012-10-16 16:36                               ` Eli Zaretskii
2012-10-19 17:01                                 ` Paul Eggert
2012-10-19 18:41                                   ` Eli Zaretskii
2012-10-19 18:54                                     ` Paul Eggert
2012-10-19 19:05                                       ` Glenn Morris
2012-10-19 19:36                                         ` Paul Eggert
2012-10-20  2:25                                           ` Richard Stallman
2012-10-20  4:36                                             ` Paul Eggert
2012-10-21  1:44                                           ` Glenn Morris
2012-10-21  2:52                                             ` Paul Eggert
2012-10-21  4:24                                               ` Glenn Morris
2012-10-22  6:03                                                 ` Paul Eggert
2012-10-22 17:19                                                   ` Eli Zaretskii
2012-10-22 20:33                                                     ` Paul Eggert
2012-10-22 21:04                                                       ` Eli Zaretskii
2012-10-22 21:30                                                         ` Paul Eggert
2012-10-23  0:40                                                           ` Stefan Monnier
2012-10-23  1:46                                                             ` Paul Eggert
2012-10-23  3:49                                                               ` Eli Zaretskii
2012-10-23  3:47                                                           ` Eli Zaretskii
2012-10-23  5:07                                                             ` Paul Eggert
2012-10-23 16:44                                                               ` Eli Zaretskii
2012-10-23 19:27                                                                 ` Paul Eggert
2012-10-23 19:50                                                                   ` Eli Zaretskii
2012-10-23 20:01                                                                     ` Paul Eggert
2012-10-23 23:15                                                                   ` Andy Moreton
2012-10-24  3:51                                                                     ` Eli Zaretskii
2012-10-19 19:10                                       ` Eli Zaretskii
2012-11-13  2:19 ` bug#12632: updated version of the patch Paul Eggert
2012-11-14  5:10   ` Paul Eggert

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=507B010F.20105@cs.ucla.edu \
    --to=eggert@cs.ucla.edu \
    --cc=12632@debbugs.gnu.org \
    --cc=eliz@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.