maint: update copyright
[gnulib.git] / lib / userspec.c
index 8cf6239..1be9266 100644 (file)
@@ -1,11 +1,11 @@
 /* userspec.c -- Parse a user and group string.
-   Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2004 Free Software
+   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
 
-#if HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-#include <alloca.h>
+#include <config.h>
 
 /* Specification.  */
 #include "userspec.h"
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <pwd.h>
 #include <stdlib.h>
 #include <string.h>
 
-#if HAVE_UNISTD_H
-# include <unistd.h>
-#endif
+#include <unistd.h>
 
-#include "strdup.h"
-#include "posixver.h"
+#include "intprops.h"
+#include "inttostr.h"
 #include "xalloc.h"
 #include "xstrtol.h"
 
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
-#ifndef _POSIX_VERSION
-struct passwd *getpwnam ();
-struct group *getgrnam ();
-struct group *getgrgid ();
-#endif
-
 #ifndef HAVE_ENDGRENT
 # define endgrent() ((void) 0)
 #endif
@@ -67,14 +55,6 @@ struct group *getgrgid ();
 # define endpwent() ((void) 0)
 #endif
 
-/* The extra casts work around common compiler bugs.  */
-#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
-/* The outer cast is needed to work around a bug in Cray C 5.0.3.0.
-   It is necessary at least when t == time_t.  */
-#define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \
-                             ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0))
-#define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t)))
-
 #ifndef UID_T_MAX
 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
 #endif
@@ -91,118 +71,84 @@ struct group *getgrgid ();
 # define MAXGID GID_T_MAX
 #endif
 
-/* Perform the equivalent of the statement `dest = strdup (src);',
-   but obtaining storage via alloca instead of from the heap.  */
-
-#define V_STRDUP(dest, src)                                            \
-  do                                                                   \
-    {                                                                  \
-      int _len = strlen ((src));                                       \
-      (dest) = (char *) alloca (_len + 1);                             \
-      strcpy (dest, src);                                              \
-    }                                                                  \
-  while (0)
+#ifdef __DJGPP__
 
 /* ISDIGIT differs from isdigit, as follows:
-   - Its arg may be any int or unsigned int; it need not be an unsigned char.
-   - It's guaranteed to evaluate its argument exactly once.
+   - 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_LOCALE 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) (c) - '0' <= 9)
+   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)
 
-/* Return nonzero if STR represents an unsigned decimal integer,
-   otherwise return 0. */
+/* Return true if STR represents an unsigned decimal integer.  */
 
-static int
+static bool
 is_number (const char *str)
 {
-  for (; *str; str++)
-    if (!ISDIGIT (*str))
-      return 0;
-  return 1;
-}
-
-/* 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 ":" separator is given,
-   use the given user's login group.
-   If SPEC_ARG 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_ARG 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.
+  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 (const char *spec_arg, uid_t *uid, gid_t *gid,
-                char **username_arg, char **groupname_arg)
+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 *E_invalid_user = N_("invalid user");
   static const char *E_invalid_group = N_("invalid group");
-  static const char *E_bad_spec =
-    N_("cannot get the login group of a numeric UID");
-  static const char *E_cannot_omit_both =
-    N_("cannot omit both user and group");
+  static const char *E_bad_spec = N_("invalid spec");
 
   const char *error_msg;
-  char *spec;                  /* A copy we can write on.  */
   struct passwd *pwd;
   struct group *grp;
-  char *g, *u, *separator;
-  char *groupname;
-  int maybe_retry = 0;
-  char *dot = NULL;
+  char *u;
+  char const *g;
+  char *gname = NULL;
+  uid_t unum = *uid;
+  gid_t gnum = gid ? *gid : -1;
 
   error_msg = NULL;
-  *username_arg = *groupname_arg = NULL;
-  groupname = NULL;
-
-  V_STRDUP (spec, spec_arg);
+  if (username)
+    *username = NULL;
+  if (groupname)
+    *groupname = NULL;
 
-  /* Find the POSIX `:' separator if there is one.  */
-  separator = strchr (spec, ':');
+  /* 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.  */
 
-  /* If there is no colon, then see if there's a `.'.  */
-  if (separator == NULL && posix2_version () < 200112)
+  u = NULL;
+  if (separator == NULL)
     {
-      dot = strchr (spec, '.');
-      /* If there's no colon but there is a `.', then first look up the
-        whole spec, in case it's an OWNER name that includes a dot.
-        If that fails, then we'll try again, but interpreting the `.'
-        as a separator.  */
-      /* FIXME: accepting `.' as the separator is contrary to POSIX.
-        someday we should drop support for this.  */
-      if (dot)
-       maybe_retry = 1;
+      if (*spec)
+        u = xstrdup (spec);
+    }
+  else
+    {
+      size_t ulen = separator - spec;
+      if (ulen != 0)
+        {
+          u = xmemdup (spec, ulen + 1);
+          u[ulen] = '\0';
+        }
     }
-
- retry:
-
-  /* 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);
 
   g = (separator == NULL || *(separator + 1) == '\0'
        ? NULL
        : separator + 1);
 
-  if (u == NULL && g == NULL)
-    return _(E_cannot_omit_both);
-
 #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.  */
+     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))
@@ -211,114 +157,127 @@ parse_user_spec (const char *spec_arg, uid_t *uid, gid_t *gid,
 
   if (u != NULL)
     {
-      pwd = getpwnam (u);
+      /* If it starts with "+", skip the look-up.  */
+      pwd = (*u == '+' ? NULL : getpwnam (u));
       if (pwd == NULL)
-       {
-
-         if (!is_number (u))
-           error_msg = E_invalid_user;
-         else
-           {
-             int use_login_group;
-             use_login_group = (separator != NULL && g == NULL);
-             if (use_login_group)
-               error_msg = E_bad_spec;
-             else
-               {
-                 unsigned long int tmp_long;
-                 if (xstrtoul (u, NULL, 0, &tmp_long, NULL) != LONGINT_OK
-                     || tmp_long > MAXUID)
-                   return _(E_invalid_user);
-                 *uid = tmp_long;
-               }
-           }
-       }
+        {
+          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));
-                 V_STRDUP (groupname, uint_buf);
-               }
-             else
-               {
-                 V_STRDUP (groupname, grp->gr_name);
-               }
-             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 (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 (!is_number (g))
-           error_msg = E_invalid_group;
-         else
-           {
-             unsigned long int tmp_long;
-             if (xstrtoul (g, NULL, 0, &tmp_long, NULL) != LONGINT_OK
-                 || tmp_long > MAXGID)
-               return _(E_invalid_group);
-             *gid = tmp_long;
-           }
-       }
+        {
+          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)
-       V_STRDUP (groupname, g);
+        gnum = grp->gr_gid;
+      endgrent ();              /* Save a file descriptor.  */
+      gname = xstrdup (g);
     }
 
   if (error_msg == NULL)
     {
-      if (u != NULL)
-       {
-         *username_arg = strdup (u);
-         if (*username_arg == NULL)
-           error_msg = xalloc_msg_memory_exhausted;
-       }
-
-      if (groupname != NULL && error_msg == NULL)
-       {
-         *groupname_arg = strdup (groupname);
-         if (*groupname_arg == NULL)
-           {
-             if (*username_arg != NULL)
-               {
-                 free (*username_arg);
-                 *username_arg = NULL;
-               }
-             error_msg = xalloc_msg_memory_exhausted;
-           }
-       }
+      *uid = unum;
+      if (gid)
+        *gid = gnum;
+      if (username)
+        {
+          *username = u;
+          u = NULL;
+        }
+      if (groupname)
+        {
+          *groupname = gname;
+          gname = NULL;
+        }
     }
 
-  if (error_msg && maybe_retry)
+  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)
     {
-      maybe_retry = 0;
-      separator = dot;
-      error_msg = NULL;
-      goto retry;
+      /* 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);
+  return error_msg;
 }
 
 #ifdef TEST
@@ -341,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:
+*/