mirror of
https://github.com/coreutils/coreutils.git
synced 2024-12-03 23:13:50 +08:00
302 lines
7.2 KiB
C
302 lines
7.2 KiB
C
/* userspec.c -- Parse a user and group string.
|
|
Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2005 Free Software
|
|
Foundation, Inc.
|
|
|
|
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.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
|
|
|
|
/* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
/* Specification. */
|
|
#include "userspec.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
|
|
#if HAVE_SYS_PARAM_H
|
|
# include <sys/param.h>
|
|
#endif
|
|
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include "intprops.h"
|
|
#include "inttostr.h"
|
|
#include "strdup.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 HAVE_ENDPWENT
|
|
# define endpwent() ((void) 0)
|
|
#endif
|
|
|
|
#ifndef UID_T_MAX
|
|
# define UID_T_MAX TYPE_MAXIMUM (uid_t)
|
|
#endif
|
|
|
|
#ifndef GID_T_MAX
|
|
# define GID_T_MAX TYPE_MAXIMUM (gid_t)
|
|
#endif
|
|
|
|
/* 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
|
|
|
|
/* 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.
|
|
- 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 int) (c) - '0' <= 9)
|
|
|
|
#ifdef __DJGPP__
|
|
|
|
/* 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
|
|
|
|
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");
|
|
|
|
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;
|
|
|
|
error_msg = NULL;
|
|
*username = *groupname = NULL;
|
|
|
|
/* 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)
|
|
{
|
|
pwd = getpwnam (u);
|
|
if (pwd == NULL)
|
|
{
|
|
bool use_login_group = (separator != NULL && g == NULL);
|
|
if (use_login_group)
|
|
error_msg = E_bad_spec;
|
|
else
|
|
{
|
|
unsigned long int tmp;
|
|
if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
|
|
&& tmp <= MAXUID)
|
|
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. */
|
|
grp = getgrnam (g);
|
|
if (grp == NULL)
|
|
{
|
|
unsigned long int tmp;
|
|
if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK && tmp <= MAXGID)
|
|
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;
|
|
*gid = gnum;
|
|
*username = u;
|
|
*groupname = gname;
|
|
u = NULL;
|
|
}
|
|
else
|
|
free (gname);
|
|
|
|
free (u);
|
|
return _(error_msg);
|
|
}
|
|
|
|
/* Extract from SPEC, 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 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 = strchr (spec, ':');
|
|
char const *error_msg =
|
|
parse_with_separator (spec, colon, uid, gid, username, groupname);
|
|
|
|
if (!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 a 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 TEST
|
|
|
|
# define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
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));
|
|
}
|
|
|
|
exit (0);
|
|
}
|
|
|
|
#endif
|