Avoid symlink attack in localcharset module. Use fcntl_h.m4 from gnulib.
diff --git a/libcharset/ChangeLog b/libcharset/ChangeLog
index 318f46f..7e41ad9 100644
--- a/libcharset/ChangeLog
+++ b/libcharset/ChangeLog
@@ -1,3 +1,8 @@
+2009-10-18  Bruno Haible  <bruno@clisp.org>
+
+	* configure.ac: Invoke gl_FCNTL_O_FLAGS.
+	* INTEGRATE: Mention fcntl_h.m4 and the gl_FCNTL_O_FLAGS invocation.
+
 2009-08-15  Bruno Haible  <bruno@clisp.org>
 
 	* Makefile.devel (AUTOCONF, AUTOHEADER): Switch to version 2.64.
diff --git a/libcharset/INTEGRATE b/libcharset/INTEGRATE
index f4aa1de..2eb1b03 100644
--- a/libcharset/INTEGRATE
+++ b/libcharset/INTEGRATE
@@ -4,13 +4,14 @@
   ref-del.sin) and the include file (include/localcharset.h) into your
   package.
 
-* Add the m4/ files (codeset.m4, glibc21.m4) to your aclocal.m4 file or, if
-  you are using automake, to your m4/ directory.
+* Add the m4/ files (codeset.m4, fcntl_h.m4, glibc21.m4) to your aclocal.m4
+  file or, if you are using automake, to your m4/ directory.
 
 * Add the following lines to your configure.ac file:
 
     AC_CANONICAL_HOST
     AM_LANGINFO_CODESET
+    gl_FCNTL_O_FLAGS
     jm_GLIBC21
     AC_CHECK_HEADERS(stddef.h stdlib.h string.h)
 
diff --git a/libcharset/configure.ac b/libcharset/configure.ac
index 5e6a23f..8c29191 100644
--- a/libcharset/configure.ac
+++ b/libcharset/configure.ac
@@ -61,6 +61,7 @@
 dnl           checks for header files and functions
 
 AM_LANGINFO_CODESET
+gl_FCNTL_O_FLAGS
 gl_GLIBC21
 AC_CHECK_FUNCS([setlocale])
 
diff --git a/libcharset/lib/ChangeLog b/libcharset/lib/ChangeLog
index f46fe98..375d23f 100644
--- a/libcharset/lib/ChangeLog
+++ b/libcharset/lib/ChangeLog
@@ -1,3 +1,11 @@
+2009-10-18  Bruno Haible  <bruno@clisp.org>
+
+	Avoid symlink attack in localcharset module.
+	* localcharset.c: Include <fcntl.h>, <unistd.h>.
+	(O_NOFOLLOW): Define fallback.
+	(get_charset_aliases): Don't open the file if it is a symbolic link.
+	Reported by Fergal Glynn <fglynn@veracode.com>.
+
 2009-03-26  Bruno Haible  <bruno@clisp.org>
 
 	* Makefile.in (install, installdir): Ensure that $(DESTDIR)$(libdir)
diff --git a/libcharset/lib/localcharset.c b/libcharset/lib/localcharset.c
index 434fc7c..e808967 100644
--- a/libcharset/lib/localcharset.c
+++ b/libcharset/lib/localcharset.c
@@ -24,6 +24,7 @@
 /* Specification.  */
 #include "localcharset.h"
 
+#include <fcntl.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <string.h>
@@ -45,6 +46,7 @@
 #endif
 
 #if !defined WIN32_NATIVE
+# include <unistd.h>
 # if HAVE_LANGINFO_CODESET
 #  include <langinfo.h>
 # else
@@ -76,6 +78,11 @@
 # include "configmake.h"
 #endif
 
+/* Define O_NOFOLLOW to 0 on platforms where it does not exist.  */
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
   /* Win32, Cygwin, OS/2, DOS */
 # define ISSLASH(C) ((C) == '/' || (C) == '\\')
@@ -118,7 +125,6 @@
   if (cp == NULL)
     {
 #if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__)
-      FILE *fp;
       const char *dir;
       const char *base = "charset.alias";
       char *file_name;
@@ -144,77 +150,105 @@
 	  }
       }
 
-      if (file_name == NULL || (fp = fopen (file_name, "r")) == NULL)
-	/* Out of memory or file not found, treat it as empty.  */
+      if (file_name == NULL)
+	/* Out of memory.  Treat the file as empty.  */
 	cp = "";
       else
 	{
-	  /* Parse the file's contents.  */
-	  char *res_ptr = NULL;
-	  size_t res_size = 0;
+	  int fd;
 
-	  for (;;)
-	    {
-	      int c;
-	      char buf1[50+1];
-	      char buf2[50+1];
-	      size_t l1, l2;
-	      char *old_res_ptr;
-
-	      c = getc (fp);
-	      if (c == EOF)
-		break;
-	      if (c == '\n' || c == ' ' || c == '\t')
-		continue;
-	      if (c == '#')
-		{
-		  /* Skip comment, to end of line.  */
-		  do
-		    c = getc (fp);
-		  while (!(c == EOF || c == '\n'));
-		  if (c == EOF)
-		    break;
-		  continue;
-		}
-	      ungetc (c, fp);
-	      if (fscanf (fp, "%50s %50s", buf1, buf2) < 2)
-		break;
-	      l1 = strlen (buf1);
-	      l2 = strlen (buf2);
-	      old_res_ptr = res_ptr;
-	      if (res_size == 0)
-		{
-		  res_size = l1 + 1 + l2 + 1;
-		  res_ptr = (char *) malloc (res_size + 1);
-		}
-	      else
-		{
-		  res_size += l1 + 1 + l2 + 1;
-		  res_ptr = (char *) realloc (res_ptr, res_size + 1);
-		}
-	      if (res_ptr == NULL)
-		{
-		  /* Out of memory. */
-		  res_size = 0;
-		  if (old_res_ptr != NULL)
-		    free (old_res_ptr);
-		  break;
-		}
-	      strcpy (res_ptr + res_size - (l2 + 1) - (l1 + 1), buf1);
-	      strcpy (res_ptr + res_size - (l2 + 1), buf2);
-	    }
-	  fclose (fp);
-	  if (res_size == 0)
+	  /* Open the file.  Reject symbolic links on platforms that support
+	     O_NOFOLLOW.  This is a security feature.  Without it, an attacker
+	     could retrieve parts of the contents (namely, the tail of the
+	     first line that starts with "* ") of an arbitrary file by placing
+	     a symbolic link to that file under the name "charset.alias" in
+	     some writable directory and defining the environment variable
+	     CHARSETALIASDIR to point to that directory.  */
+	  fd = open (file_name,
+		     O_RDONLY | (HAVE_WORKING_O_NOFOLLOW ? O_NOFOLLOW : 0));
+	  if (fd < 0)
+	    /* File not found.  Treat it as empty.  */
 	    cp = "";
 	  else
 	    {
-	      *(res_ptr + res_size) = '\0';
-	      cp = res_ptr;
-	    }
-	}
+	      FILE *fp;
 
-      if (file_name != NULL)
-	free (file_name);
+	      fp = fdopen (fd, "r");
+	      if (fp == NULL)
+		{
+		  /* Out of memory.  Treat the file as empty.  */
+		  close (fd);
+		  cp = "";
+		}
+	      else
+		{
+		  /* Parse the file's contents.  */
+		  char *res_ptr = NULL;
+		  size_t res_size = 0;
+
+		  for (;;)
+		    {
+		      int c;
+		      char buf1[50+1];
+		      char buf2[50+1];
+		      size_t l1, l2;
+		      char *old_res_ptr;
+
+		      c = getc (fp);
+		      if (c == EOF)
+			break;
+		      if (c == '\n' || c == ' ' || c == '\t')
+			continue;
+		      if (c == '#')
+			{
+			  /* Skip comment, to end of line.  */
+			  do
+			    c = getc (fp);
+			  while (!(c == EOF || c == '\n'));
+			  if (c == EOF)
+			    break;
+			  continue;
+			}
+		      ungetc (c, fp);
+		      if (fscanf (fp, "%50s %50s", buf1, buf2) < 2)
+			break;
+		      l1 = strlen (buf1);
+		      l2 = strlen (buf2);
+		      old_res_ptr = res_ptr;
+		      if (res_size == 0)
+			{
+			  res_size = l1 + 1 + l2 + 1;
+			  res_ptr = (char *) malloc (res_size + 1);
+			}
+		      else
+			{
+			  res_size += l1 + 1 + l2 + 1;
+			  res_ptr = (char *) realloc (res_ptr, res_size + 1);
+			}
+		      if (res_ptr == NULL)
+			{
+			  /* Out of memory. */
+			  res_size = 0;
+			  if (old_res_ptr != NULL)
+			    free (old_res_ptr);
+			  break;
+			}
+		      strcpy (res_ptr + res_size - (l2 + 1) - (l1 + 1), buf1);
+		      strcpy (res_ptr + res_size - (l2 + 1), buf2);
+		    }
+		  fclose (fp);
+		  if (res_size == 0)
+		    cp = "";
+		  else
+		    {
+		      *(res_ptr + res_size) = '\0';
+		      cp = res_ptr;
+		    }
+		}
+	    }
+
+	  free (file_name);
+	}
 
 #else
 
diff --git a/libcharset/m4/fcntl_h.m4 b/libcharset/m4/fcntl_h.m4
new file mode 100644
index 0000000..223fa48
--- /dev/null
+++ b/libcharset/m4/fcntl_h.m4
@@ -0,0 +1,108 @@
+# serial 6
+# Configure fcntl.h.
+dnl Copyright (C) 2006, 2007, 2009 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.
+
+dnl Written by Paul Eggert.
+
+AC_DEFUN([gl_FCNTL_H],
+[
+  AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
+  AC_REQUIRE([gl_FCNTL_O_FLAGS])
+  gl_CHECK_NEXT_HEADERS([fcntl.h])
+  FCNTL_H='fcntl.h'
+  AC_SUBST([FCNTL_H])
+])
+
+# Test whether the flags O_NOATIME and O_NOFOLLOW actually work.
+# Define HAVE_WORKING_O_NOATIME to 1 if O_NOATIME works, or to 0 otherwise.
+# Define HAVE_WORKING_O_NOFOLLOW to 1 if O_NOFOLLOW works, or to 0 otherwise.
+AC_DEFUN([gl_FCNTL_O_FLAGS],
+[
+  dnl Persuade glibc <fcntl.h> to define O_NOATIME and O_NOFOLLOW.
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+  AC_CACHE_CHECK([for working fcntl.h], [gl_cv_header_working_fcntl_h],
+    [AC_RUN_IFELSE(
+       [AC_LANG_PROGRAM(
+	  [[#include <sys/types.h>
+	   #include <sys/stat.h>
+	   #include <unistd.h>
+	   #include <fcntl.h>
+	   #ifndef O_NOATIME
+	    #define O_NOATIME 0
+	   #endif
+	   #ifndef O_NOFOLLOW
+	    #define O_NOFOLLOW 0
+	   #endif
+	   static int const constants[] =
+	    {
+	      O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND,
+	      O_NONBLOCK, O_SYNC, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY
+	    };
+	  ]],
+	  [[
+	    int status = !constants;
+	    {
+	      static char const sym[] = "conftest.sym";
+	      if (symlink (".", sym) != 0
+		  || close (open (sym, O_RDONLY | O_NOFOLLOW)) == 0)
+		status |= 32;
+	      unlink (sym);
+	    }
+	    {
+	      static char const file[] = "confdefs.h";
+	      int fd = open (file, O_RDONLY | O_NOATIME);
+	      char c;
+	      struct stat st0, st1;
+	      if (fd < 0
+		  || fstat (fd, &st0) != 0
+		  || sleep (1) != 0
+		  || read (fd, &c, 1) != 1
+		  || close (fd) != 0
+		  || stat (file, &st1) != 0
+		  || st0.st_atime != st1.st_atime)
+		status |= 64;
+	    }
+	    return status;]])],
+       [gl_cv_header_working_fcntl_h=yes],
+       [case $? in #(
+	32) gl_cv_header_working_fcntl_h='no (bad O_NOFOLLOW)';; #(
+	64) gl_cv_header_working_fcntl_h='no (bad O_NOATIME)';; #(
+	96) gl_cv_header_working_fcntl_h='no (bad O_NOATIME, O_NOFOLLOW)';; #(
+	 *) gl_cv_header_working_fcntl_h='no';;
+	esac],
+       [gl_cv_header_working_fcntl_h=cross-compiling])])
+
+  case $gl_cv_header_working_fcntl_h in #(
+  *O_NOATIME* | no | cross-compiling) ac_val=0;; #(
+  *) ac_val=1;;
+  esac
+  AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOATIME], [$ac_val],
+    [Define to 1 if O_NOATIME works.])
+
+  case $gl_cv_header_working_fcntl_h in #(
+  *O_NOFOLLOW* | no | cross-compiling) ac_val=0;; #(
+  *) ac_val=1;;
+  esac
+  AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOFOLLOW], [$ac_val],
+    [Define to 1 if O_NOFOLLOW works.])
+])
+
+AC_DEFUN([gl_FCNTL_MODULE_INDICATOR],
+[
+  dnl Use AC_REQUIRE here, so that the default settings are expanded once only.
+  AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
+  GNULIB_[]m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./-],[ABCDEFGHIJKLMNOPQRSTUVWXYZ___])=1
+])
+
+AC_DEFUN([gl_FCNTL_H_DEFAULTS],
+[
+  GNULIB_OPEN=0;    AC_SUBST([GNULIB_OPEN])
+  GNULIB_OPENAT=0;  AC_SUBST([GNULIB_OPENAT])
+  dnl Assume proper GNU behavior unless another module says otherwise.
+  HAVE_OPENAT=1;    AC_SUBST([HAVE_OPENAT])
+  REPLACE_OPEN=0;   AC_SUBST([REPLACE_OPEN])
+  REPLACE_OPENAT=0; AC_SUBST([REPLACE_OPENAT])
+])