(root)/
coreutils-9.4/
lib/
userspec.c
       1  /* userspec.c -- Parse a user and group string.
       2     Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2023 Free Software
       3     Foundation, Inc.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation, either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
      19  
      20  #include <config.h>
      21  
      22  /* Specification.  */
      23  #include "userspec.h"
      24  
      25  #include <stdio.h>
      26  #include <sys/types.h>
      27  #include <pwd.h>
      28  #include <grp.h>
      29  
      30  #if HAVE_SYS_PARAM_H
      31  # include <sys/param.h>
      32  #endif
      33  
      34  #include <limits.h>
      35  #include <stdlib.h>
      36  #include <string.h>
      37  
      38  #include <unistd.h>
      39  
      40  #include "intprops.h"
      41  #include "inttostr.h"
      42  #include "xalloc.h"
      43  #include "xstrtol.h"
      44  
      45  #include "gettext.h"
      46  #define _(msgid) gettext (msgid)
      47  #define N_(msgid) msgid
      48  
      49  #ifndef HAVE_ENDGRENT
      50  # define endgrent() ((void) 0)
      51  #endif
      52  
      53  #ifndef HAVE_ENDPWENT
      54  # define endpwent() ((void) 0)
      55  #endif
      56  
      57  #ifndef UID_T_MAX
      58  # define UID_T_MAX TYPE_MAXIMUM (uid_t)
      59  #endif
      60  
      61  #ifndef GID_T_MAX
      62  # define GID_T_MAX TYPE_MAXIMUM (gid_t)
      63  #endif
      64  
      65  /* MAXUID may come from limits.h or sys/params.h.  */
      66  #ifndef MAXUID
      67  # define MAXUID UID_T_MAX
      68  #endif
      69  #ifndef MAXGID
      70  # define MAXGID GID_T_MAX
      71  #endif
      72  
      73  #ifdef __DJGPP__
      74  
      75  /* ISDIGIT differs from isdigit, as follows:
      76     - Its arg may be any int or unsigned int; it need not be an unsigned char
      77       or EOF.
      78     - It's typically faster.
      79     POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
      80     isdigit unless it's important to use the locale's definition
      81     of "digit" even when the host does not conform to POSIX.  */
      82  # define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
      83  
      84  /* Return true if STR represents an unsigned decimal integer.  */
      85  
      86  static bool
      87  is_number (const char *str)
      88  {
      89    do
      90      {
      91        if (!ISDIGIT (*str))
      92          return false;
      93      }
      94    while (*++str);
      95  
      96    return true;
      97  }
      98  #endif
      99  
     100  static char const *
     101  parse_with_separator (char const *spec, char const *separator,
     102                        uid_t *uid, gid_t *gid,
     103                        char **username, char **groupname)
     104  {
     105    const char *error_msg;
     106    struct passwd *pwd;
     107    struct group *grp;
     108    char *u;
     109    char const *g;
     110    char *gname = NULL;
     111    uid_t unum = *uid;
     112    gid_t gnum = gid ? *gid : -1;
     113  
     114    error_msg = NULL;
     115    if (username)
     116      *username = NULL;
     117    if (groupname)
     118      *groupname = NULL;
     119  
     120    /* Set U and G to nonzero length strings corresponding to user and
     121       group specifiers or to NULL.  If U is not NULL, it is a newly
     122       allocated string.  */
     123  
     124    u = NULL;
     125    if (separator == NULL)
     126      {
     127        if (*spec)
     128          u = xstrdup (spec);
     129      }
     130    else
     131      {
     132        idx_t ulen = separator - spec;
     133        if (ulen != 0)
     134          {
     135            u = ximemdup (spec, ulen + 1);
     136            u[ulen] = '\0';
     137          }
     138      }
     139  
     140    g = (separator == NULL || *(separator + 1) == '\0'
     141         ? NULL
     142         : separator + 1);
     143  
     144  #ifdef __DJGPP__
     145    /* Pretend that we are the user U whose group is G.  This makes
     146       pwd and grp functions "know" about the UID and GID of these.  */
     147    if (u && !is_number (u))
     148      setenv ("USER", u, 1);
     149    if (g && !is_number (g))
     150      setenv ("GROUP", g, 1);
     151  #endif
     152  
     153    if (u != NULL)
     154      {
     155        /* If it starts with "+", skip the look-up.  */
     156        pwd = (*u == '+' ? NULL : getpwnam (u));
     157        if (pwd == NULL)
     158          {
     159            username = NULL;
     160            bool use_login_group = (separator != NULL && g == NULL);
     161            if (use_login_group)
     162              {
     163                /* If there is no group,
     164                   then there may not be a trailing ":", either.  */
     165                error_msg = N_("invalid spec");
     166              }
     167            else
     168              {
     169                unsigned long int tmp;
     170                if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
     171                    && tmp <= MAXUID && (uid_t) tmp != (uid_t) -1)
     172                  unum = tmp;
     173                else
     174                  error_msg = N_("invalid user");
     175              }
     176          }
     177        else
     178          {
     179            unum = pwd->pw_uid;
     180            if (g == NULL && separator != NULL)
     181              {
     182                /* A separator was given, but a group was not specified,
     183                   so get the login group.  */
     184                char buf[INT_BUFSIZE_BOUND (uintmax_t)];
     185                gnum = pwd->pw_gid;
     186                grp = getgrgid (gnum);
     187                gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
     188                endgrent ();
     189              }
     190          }
     191        endpwent ();
     192      }
     193  
     194    if (g != NULL && error_msg == NULL)
     195      {
     196        /* Explicit group.  */
     197        /* If it starts with "+", skip the look-up.  */
     198        grp = (*g == '+' ? NULL : getgrnam (g));
     199        if (grp == NULL)
     200          {
     201            groupname = NULL;
     202            unsigned long int tmp;
     203            if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK
     204                && tmp <= MAXGID && (gid_t) tmp != (gid_t) -1)
     205              gnum = tmp;
     206            else
     207              error_msg = N_("invalid group");
     208          }
     209        else
     210          gnum = grp->gr_gid;
     211        endgrent ();              /* Save a file descriptor.  */
     212        gname = xstrdup (g);
     213      }
     214  
     215    if (error_msg == NULL)
     216      {
     217        *uid = unum;
     218        if (gid)
     219          *gid = gnum;
     220        if (username)
     221          {
     222            *username = u;
     223            u = NULL;
     224          }
     225        if (groupname)
     226          {
     227            *groupname = gname;
     228            gname = NULL;
     229          }
     230      }
     231  
     232    free (u);
     233    free (gname);
     234    return error_msg ? _(error_msg) : NULL;
     235  }
     236  
     237  /* Extract from SPEC, which has the form "[user][:.][group]",
     238     a USERNAME, UID U, GROUPNAME, and GID G.
     239     If the GID parameter is NULL the entire SPEC is treated as a user.
     240     If the USERNAME and GROUPNAME parameters are NULL they're ignored.
     241     Either user or group, or both, must be present.
     242     If the group is omitted but the separator is given,
     243     use the given user's login group.
     244     If SPEC contains a ':', then use that as the separator, ignoring
     245     any '.'s.  If there is no ':', but there is a '.', then first look
     246     up the entire SPEC as a login name.  If that look-up fails, then
     247     try again interpreting the '.'  as a separator.
     248  
     249     USERNAME and GROUPNAME will be in newly malloc'd memory.
     250     Either one might be NULL instead, indicating that it was not
     251     given and the corresponding numeric ID was left unchanged.
     252  
     253     Return NULL if successful, a static error message string if not.
     254     If PWARN is null, return NULL instead of a warning;
     255     otherwise, set *PWARN to true depending on whether returning a warning.  */
     256  
     257  char const *
     258  parse_user_spec_warn (char const *spec, uid_t *uid, gid_t *gid,
     259                        char **username, char **groupname, bool *pwarn)
     260  {
     261    char const *colon = gid ? strchr (spec, ':') : NULL;
     262    char const *error_msg =
     263      parse_with_separator (spec, colon, uid, gid, username, groupname);
     264    bool warn = false;
     265  
     266    if (gid && !colon && error_msg)
     267      {
     268        /* If there's no colon but there is a dot, and if looking up the
     269           whole spec failed (i.e., the spec is not an owner name that
     270           includes a dot), then try again, but interpret the dot as a
     271           separator.  This is a compatible extension to POSIX, since
     272           the POSIX-required behavior is always tried first.  */
     273  
     274        char const *dot = strchr (spec, '.');
     275        if (dot
     276            && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
     277          {
     278            warn = true;
     279            error_msg = pwarn ? N_("warning: '.' should be ':'") : NULL;
     280          }
     281      }
     282  
     283    if (pwarn)
     284      *pwarn = warn;
     285    return error_msg;
     286  }
     287  
     288  /* Like parse_user_spec_warn, but generate only errors; no warnings.  */
     289  
     290  char const *
     291  parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
     292                   char **username, char **groupname)
     293  {
     294    return parse_user_spec_warn (spec, uid, gid, username, groupname, NULL);
     295  }
     296  
     297  #ifdef TEST
     298  
     299  # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
     300  
     301  int
     302  main (int argc, char **argv)
     303  {
     304    int i;
     305  
     306    for (i = 1; i < argc; i++)
     307      {
     308        const char *e;
     309        char *username, *groupname;
     310        uid_t uid;
     311        gid_t gid;
     312        char *tmp;
     313  
     314        tmp = strdup (argv[i]);
     315        e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
     316        free (tmp);
     317        printf ("%s: %lu %lu %s %s %s\n",
     318                argv[i],
     319                (unsigned long int) uid,
     320                (unsigned long int) gid,
     321                NULL_CHECK (username),
     322                NULL_CHECK (groupname),
     323                NULL_CHECK (e));
     324      }
     325  
     326    exit (0);
     327  }
     328  
     329  #endif
     330  
     331  /*
     332  Local Variables:
     333  indent-tabs-mode: nil
     334  End:
     335  */