maint: update copyright
[gnulib.git] / lib / userspec.c
index 37b7980..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)
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#include <limits.h>
+#include <stdlib.h>
 #include <string.h>
-#ifndef index
-#define index strchr
+
+#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
-#else
-#include <strings.h>
+
+#ifndef HAVE_ENDPWENT
+# define endpwent() ((void) 0)
 #endif
 
-#ifdef STDC_HEADERS
-#include <stdlib.h>
-#else
-char *malloc ();
+#ifndef UID_T_MAX
+# define UID_T_MAX TYPE_MAXIMUM (uid_t)
 #endif
 
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
+#ifndef GID_T_MAX
+# define GID_T_MAX TYPE_MAXIMUM (gid_t)
 #endif
 
-#ifndef _POSIX_VERSION
-struct passwd *getpwnam ();
-struct group *getgrnam ();
-struct group *getgrgid ();
+/* 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
+
+#ifdef __DJGPP__
 
-#ifdef _POSIX_SOURCE
-#define endpwent()
-#define endgrent()
+/* 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)
+
+/* Return true if STR represents an unsigned decimal integer.  */
+
+static bool
+is_number (const char *str)
+{
+  do
+    {
+      if (!ISDIGIT (*str))
+        return false;
+    }
+  while (*++str);
+
+  return true;
+}
 #endif
 
-#define isdigit(c) ((c) >= '0' && (c) <= '9')
+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_("invalid spec");
 
-char *strdup ();
-static int isnumber ();
+  const char *error_msg;
+  struct passwd *pwd;
+  struct group *grp;
+  char *u;
+  char const *g;
+  char *gname = NULL;
+  uid_t unum = *uid;
+  gid_t gnum = gid ? *gid : -1;
+
+  error_msg = NULL;
+  if (username)
+    *username = NULL;
+  if (groupname)
+    *groupname = NULL;
 
-/* Extract from NAME, which has the form "[user][:.][group]",
+  /* 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.  */
+
+  u = NULL;
+  if (separator == NULL)
+    {
+      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);
+
+#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)
+    {
+      /* If it starts with "+", skip the look-up.  */
+      pwd = (*u == '+' ? NULL : getpwnam (u));
+      if (pwd == NULL)
+        {
+          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
+        {
+          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.  */
+      /* If it starts with "+", skip the look-up.  */
+      grp = (*g == '+' ? NULL : getgrnam (g));
+      if (grp == NULL)
+        {
+          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
+        gnum = grp->gr_gid;
+      endgrent ();              /* Save a file descriptor.  */
+      gname = xstrdup (g);
+    }
+
+  if (error_msg == 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 ":" or "." separator is given,
+   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.
-   Might write NULs into NAME.
 
    Return NULL if successful, a static error message string if not.  */
 
-char *
-parse_user_spec (name, uid, gid, username, groupname)
-     char *name;
-     uid_t *uid;
-     gid_t *gid;
-     char **username, **groupname;
+char const *
+parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
+                 char **username, char **groupname)
 {
-  static char *tired = "virtual memory exhausted";
-  struct passwd *pwd;
-  struct group *grp;
-  char *cp;
-  int use_login_group = 0;
+  char const *colon = gid ? strchr (spec, ':') : NULL;
+  char const *error_msg =
+    parse_with_separator (spec, colon, uid, gid, username, groupname);
 
-  *username = *groupname = NULL;
-
-  /* Check whether a group is given.  */
-  cp = index (name, ':');
-  if (cp == NULL)
-    cp = index (name, '.');
-  if (cp != NULL)
+  if (gid && !colon && error_msg)
     {
-      *cp++ = '\0';
-      if (*cp == '\0')
-       {
-         if (cp == name + 1)
-           /* Neither user nor group given, just "." or ":".  */
-           return "can not omit both user and group";
-         else
-           /* "user.".  */
-           use_login_group = 1;
-       }
-      else
-       {
-         /* Explicit group.  */
-         *groupname = strdup (cp);
-         if (*groupname == NULL)
-           return tired;
-         grp = getgrnam (cp);
-         if (grp == NULL)
-           {
-             if (!isnumber (cp))
-               return "invalid group";
-             *gid = atoi (cp);
-           }
-         else
-           *gid = grp->gr_gid;
-         endgrent ();          /* Save a file descriptor.  */
-       }
+      /* 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;
     }
 
-  /* Parse the user name, now that any group has been removed.  */
+  return error_msg;
+}
 
-  if (name[0] == '\0')
-    /* No user name was given, just a group.  */
-    return NULL;
+#ifdef TEST
 
-  *username = strdup (name);
-  if (*username == NULL)
-    return tired;
+# define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
 
-  pwd = getpwnam (name);
-  if (pwd == NULL)
-    {
-      if (!isnumber (name))
-       return "invalid user";
-      if (use_login_group)
-       return "cannot get the login group of a numeric UID";
-      *uid = atoi (name);
-    }
-  else
+int
+main (int argc, char **argv)
+{
+  int i;
+
+  for (i = 1; i < argc; i++)
     {
-      *uid = pwd->pw_uid;
-      if (use_login_group)
-       {
-         *gid = pwd->pw_gid;
-         grp = getgrgid (pwd->pw_gid);
-         if (grp == NULL)
-           {
-             *groupname = malloc (15);
-             if (*groupname == NULL)
-               return tired;
-             sprintf (*groupname, "%u", (unsigned) (pwd->pw_gid));
-           }
-         else
-           {
-             *groupname = strdup (grp->gr_name);
-             if (*groupname == NULL)
-               return tired;
-           }
-         endgrent ();
-       }
+      const char *e;
+      char *username, *groupname;
+      uid_t uid;
+      gid_t gid;
+      char *tmp;
+
+      tmp = strdup (argv[i]);
+      e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
+      free (tmp);
+      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));
     }
-  endpwent ();
-  return NULL;
+
+  exit (0);
 }
 
-/* Return nonzero if STR represents an unsigned decimal integer,
-   otherwise return 0. */
+#endif
 
-static int
-isnumber (str)
-     char *str;
-{
-  for (; *str; str++)
-    if (!isdigit (*str))
-      return 0;
-  return 1;
-}
+/*
+Local Variables:
+indent-tabs-mode: nil
+End:
+*/