maint: update copyright
[gnulib.git] / lib / userspec.c
index 34b1d32..1be9266 100644 (file)
@@ -1,10 +1,11 @@
 /* userspec.c -- Parse a user and group string.
-   Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
+   Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2014 Free Software
+   Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   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 2, or (at your option)
-   any later version.
+   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
    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, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
-\f
-#ifdef HAVE_CONFIG_H
-#if defined (CONFIG_BROKETS)
-/* We use <config.h> instead of "config.h" so that a compilation
-   using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
-   (which it would do because it found this file in $srcdir).  */
+
 #include <config.h>
-#else
-#include "config.h"
-#endif
-#endif
 
+/* Specification.  */
+#include "userspec.h"
+
+#include <stdbool.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <pwd.h>
 #include <grp.h>
 
-#if defined(STDC_HEADERS) || defined(HAVE_STRING_H)
-#include <string.h>
-#ifndef index
-#define index strchr
-#endif
-#else
-#include <strings.h>
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
 #endif
 
-#ifdef STDC_HEADERS
+#include <limits.h>
 #include <stdlib.h>
-#else
-char *malloc ();
-#endif
+#include <string.h>
 
-#ifdef HAVE_UNISTD_H
 #include <unistd.h>
+
+#include "intprops.h"
+#include "inttostr.h"
+#include "xalloc.h"
+#include "xstrtol.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+#ifndef HAVE_ENDGRENT
+# define endgrent() ((void) 0)
 #endif
 
-#ifndef _POSIX_VERSION
-struct passwd *getpwnam ();
-struct group *getgrnam ();
-struct group *getgrgid ();
+#ifndef HAVE_ENDPWENT
+# define endpwent() ((void) 0)
 #endif
 
-#ifdef _POSIX_SOURCE
-#define endpwent()
-#define endgrent()
+#ifndef UID_T_MAX
+# define UID_T_MAX TYPE_MAXIMUM (uid_t)
 #endif
 
-#define isdigit(c) ((c) >= '0' && (c) <= '9')
+#ifndef GID_T_MAX
+# define GID_T_MAX TYPE_MAXIMUM (gid_t)
+#endif
 
-char *strdup ();
+/* MAXUID may come from limits.h or sys/params.h.  */
+#ifndef MAXUID
+# define MAXUID UID_T_MAX
+#endif
+#ifndef MAXGID
+# define MAXGID GID_T_MAX
+#endif
 
-/* Return nonzero if STR represents an unsigned decimal integer,
-   otherwise return 0. */
+#ifdef __DJGPP__
 
-static int
-isnumber (str)
-     const char *str;
-{
-  for (; *str; str++)
-    if (!isdigit (*str))
-      return 0;
-  return 1;
-}
+/* ISDIGIT differs from isdigit, as follows:
+   - Its arg may be any int or unsigned int; it need not be an unsigned char
+     or EOF.
+   - It's typically faster.
+   POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
+   isdigit unless it's important to use the locale's definition
+   of "digit" even when the host does not conform to POSIX.  */
+# define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
 
-/* Extract from NAME, which has the form "[user][:.][group]",
-   a USERNAME, UID U, GROUPNAME, and GID G.
-   Either user or group, or both, must be present.
-   If the group is omitted but the ":" or "." separator is given,
-   use the given user's login group.
+/* Return true if STR represents an unsigned decimal integer.  */
 
-   USERNAME and GROUPNAME will be in newly malloc'd memory.
-   Either one might be NULL instead, indicating that it was not
-   given and the corresponding numeric ID was left unchanged.
+static bool
+is_number (const char *str)
+{
+  do
+    {
+      if (!ISDIGIT (*str))
+        return false;
+    }
+  while (*++str);
 
-   Return NULL if successful, a static error message string if not.  */
+  return true;
+}
+#endif
 
-const char *
-parse_user_spec (spec_arg, uid, gid, username, groupname)
-     const char *spec_arg;
-     uid_t *uid;
-     gid_t *gid;
-     char **username, **groupname;
+static char const *
+parse_with_separator (char const *spec, char const *separator,
+                      uid_t *uid, gid_t *gid,
+                      char **username, char **groupname)
 {
-  static const char *tired = "virtual memory exhausted";
+  static const char *E_invalid_user = N_("invalid user");
+  static const char *E_invalid_group = N_("invalid group");
+  static const char *E_bad_spec = N_("invalid spec");
+
   const char *error_msg;
-  char *spec;                  /* A copy we can write on.  */
-  int spec_len;
   struct passwd *pwd;
   struct group *grp;
-  char *g, *u, *separator;
+  char *u;
+  char const *g;
+  char *gname = NULL;
+  uid_t unum = *uid;
+  gid_t gnum = gid ? *gid : -1;
 
   error_msg = NULL;
-  *username = *groupname = NULL;
+  if (username)
+    *username = NULL;
+  if (groupname)
+    *groupname = NULL;
 
-  spec_len = strlen (spec_arg);
-  spec = (char *) alloca (strlen (spec_arg) + 1);
-  strcpy (spec, spec_arg);
+  /* Set U and G to nonzero length strings corresponding to user and
+     group specifiers or to NULL.  If U is not NULL, it is a newly
+     allocated string.  */
 
-  /* Find the separator if there is one.  */
-  separator = index (spec, ':');
+  u = NULL;
   if (separator == NULL)
-    separator = index (spec, '.');
-
-  /* Replace separator with a NUL.  */
-  if (separator != NULL)
-    *separator = '\0';
-
-  /* Set U and G to non-zero length strings corresponding to user and
-     group specifiers or to NULL.  */
-  u = (*spec == '\0' ? NULL : spec);
+    {
+      if (*spec)
+        u = xstrdup (spec);
+    }
+  else
+    {
+      size_t ulen = separator - spec;
+      if (ulen != 0)
+        {
+          u = xmemdup (spec, ulen + 1);
+          u[ulen] = '\0';
+        }
+    }
 
   g = (separator == NULL || *(separator + 1) == '\0'
        ? NULL
        : separator + 1);
 
-  if (u == NULL && g == NULL)
-    return "can not omit both user and group";
+#ifdef __DJGPP__
+  /* Pretend that we are the user U whose group is G.  This makes
+     pwd and grp functions "know" about the UID and GID of these.  */
+  if (u && !is_number (u))
+    setenv ("USER", u, 1);
+  if (g && !is_number (g))
+    setenv ("GROUP", g, 1);
+#endif
 
   if (u != NULL)
     {
-      pwd = getpwnam (u);
+      /* If it starts with "+", skip the look-up.  */
+      pwd = (*u == '+' ? NULL : getpwnam (u));
       if (pwd == NULL)
-       {
-
-         if (!isnumber (u))
-           error_msg = "invalid user";
-         else
-           {
-             int use_login_group;
-             use_login_group = (separator != NULL && g == NULL);
-             if (use_login_group)
-               error_msg = "cannot get the login group of a numeric UID";
-             else
-               *uid = atoi (u);
-           }
-       }
+        {
+          bool use_login_group = (separator != NULL && g == NULL);
+          if (use_login_group)
+            {
+              /* If there is no group,
+                 then there may not be a trailing ":", either.  */
+              error_msg = E_bad_spec;
+            }
+          else
+            {
+              unsigned long int tmp;
+              if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
+                  && tmp <= MAXUID && (uid_t) tmp != (uid_t) -1)
+                unum = tmp;
+              else
+                error_msg = E_invalid_user;
+            }
+        }
       else
-       {
-         *uid = pwd->pw_uid;
-         if (g == NULL && separator != NULL)
-           {
-             /* A separator was given, but a group was not specified,
-                so get the login group.  */
-             *gid = pwd->pw_gid;
-             grp = getgrgid (pwd->pw_gid);
-             if (grp == NULL)
-               {
-                 /* This is enough room to hold the unsigned decimal
-                    representation of any 32-bit quantity and the trailing
-                    zero byte.  */
-                 char uint_buf[21];
-                 sprintf (uint_buf, "%u", (unsigned) (pwd->pw_gid));
-                 *groupname = strdup (uint_buf);
-               }
-             else
-               {
-                 *groupname = strdup (grp->gr_name);
-               }
-             if (*groupname == NULL)
-               error_msg = tired;
-             endgrent ();
-           }
-       }
+        {
+          unum = pwd->pw_uid;
+          if (g == NULL && separator != NULL)
+            {
+              /* A separator was given, but a group was not specified,
+                 so get the login group.  */
+              char buf[INT_BUFSIZE_BOUND (uintmax_t)];
+              gnum = pwd->pw_gid;
+              grp = getgrgid (gnum);
+              gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
+              endgrent ();
+            }
+        }
       endpwent ();
-
-      if (error_msg == NULL)
-       {
-         *username = strdup (u);
-         if (*username == NULL)
-           error_msg = tired;
-       }
     }
 
-  if (g != NULL)
+  if (g != NULL && error_msg == NULL)
     {
       /* Explicit group.  */
-      grp = getgrnam (g);
+      /* If it starts with "+", skip the look-up.  */
+      grp = (*g == '+' ? NULL : getgrnam (g));
       if (grp == NULL)
-       {
-         if (!isnumber (g))
-           error_msg = "invalid group";
-         else
-           *gid = atoi (g);
-       }
+        {
+          unsigned long int tmp;
+          if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK
+              && tmp <= MAXGID && (gid_t) tmp != (gid_t) -1)
+            gnum = tmp;
+          else
+            error_msg = E_invalid_group;
+        }
       else
-       *gid = grp->gr_gid;
-      endgrent ();             /* Save a file descriptor.  */
-
-      if (error_msg == NULL)
-       {
-         *groupname = strdup (g);
-         if (*groupname == NULL)
-           return tired;
-       }
+        gnum = grp->gr_gid;
+      endgrent ();              /* Save a file descriptor.  */
+      gname = xstrdup (g);
     }
 
-  if (error_msg)
+  if (error_msg == NULL)
     {
-      if (*groupname != NULL)
-       {
-         free (*groupname);
-         *groupname = NULL;
-       }
-      if (*username != NULL)
-       {
-         free (*username);
-         *username = NULL;
-       }
+      *uid = unum;
+      if (gid)
+        *gid = gnum;
+      if (username)
+        {
+          *username = u;
+          u = NULL;
+        }
+      if (groupname)
+        {
+          *groupname = gname;
+          gname = NULL;
+        }
+    }
+
+  free (u);
+  free (gname);
+  return _(error_msg);
+}
+
+/* Extract from SPEC, which has the form "[user][:.][group]",
+   a USERNAME, UID U, GROUPNAME, and GID G.
+   If the GID parameter is NULL the entire SPEC is treated as a user.
+   If the USERNAME and GROUPNAME parameters are NULL they're ignored.
+   Either user or group, or both, must be present.
+   If the group is omitted but the separator is given,
+   use the given user's login group.
+   If SPEC contains a ':', then use that as the separator, ignoring
+   any '.'s.  If there is no ':', but there is a '.', then first look
+   up the entire SPEC as a login name.  If that look-up fails, then
+   try again interpreting the '.'  as a separator.
+
+   USERNAME and GROUPNAME will be in newly malloc'd memory.
+   Either one might be NULL instead, indicating that it was not
+   given and the corresponding numeric ID was left unchanged.
+
+   Return NULL if successful, a static error message string if not.  */
+
+char const *
+parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
+                 char **username, char **groupname)
+{
+  char const *colon = gid ? strchr (spec, ':') : NULL;
+  char const *error_msg =
+    parse_with_separator (spec, colon, uid, gid, username, groupname);
+
+  if (gid && !colon && error_msg)
+    {
+      /* If there's no colon but there is a dot, and if looking up the
+         whole spec failed (i.e., the spec is not an owner name that
+         includes a dot), then try again, but interpret the dot as a
+         separator.  This is a compatible extension to POSIX, since
+         the POSIX-required behavior is always tried first.  */
+
+      char const *dot = strchr (spec, '.');
+      if (dot
+          && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
+        error_msg = NULL;
     }
 
   return error_msg;
 }
 
-#ifdef TESTING
+#ifdef TEST
 
-#define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
+# define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
 
 int
 main (int argc, char **argv)
@@ -249,16 +300,22 @@ main (int argc, char **argv)
       tmp = strdup (argv[i]);
       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
       free (tmp);
-      printf ("%s: %u %u %s %s %s\n",
-             argv[i],
-             (unsigned int) uid,
-             (unsigned int) gid,
-             NULL_CHECK (username),
-             NULL_CHECK (groupname),
-             NULL_CHECK (e));
+      printf ("%s: %lu %lu %s %s %s\n",
+              argv[i],
+              (unsigned long int) uid,
+              (unsigned long int) gid,
+              NULL_CHECK (username),
+              NULL_CHECK (groupname),
+              NULL_CHECK (e));
     }
 
   exit (0);
 }
 
 #endif
+
+/*
+Local Variables:
+indent-tabs-mode: nil
+End:
+*/