336 lines
8.7 KiB
C
336 lines
8.7 KiB
C
/* userspec.c -- Parse a user and group string.
|
|
Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2022 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 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
|
|
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, see <https://www.gnu.org/licenses/>. */
|
|
|
|
/* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
|
|
|
|
#include <config.h>
|
|
|
|
/* Specification. */
|
|
#include "userspec.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 "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
|
|
|
|
#ifdef __DJGPP__
|
|
|
|
/* 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
|
|
|
|
static char const *
|
|
parse_with_separator (char const *spec, char const *separator,
|
|
uid_t *uid, gid_t *gid,
|
|
char **username, char **groupname)
|
|
{
|
|
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;
|
|
|
|
/* 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
|
|
{
|
|
idx_t ulen = separator - spec;
|
|
if (ulen != 0)
|
|
{
|
|
u = ximemdup (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)
|
|
{
|
|
username = 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 = N_("invalid 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 = N_("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)
|
|
{
|
|
groupname = 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 = N_("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 ? _(error_msg) : NULL;
|
|
}
|
|
|
|
/* 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.
|
|
If PWARN is null, return NULL instead of a warning;
|
|
otherwise, set *PWARN to true depending on whether returning a warning. */
|
|
|
|
char const *
|
|
parse_user_spec_warn (char const *spec, uid_t *uid, gid_t *gid,
|
|
char **username, char **groupname, bool *pwarn)
|
|
{
|
|
char const *colon = gid ? strchr (spec, ':') : NULL;
|
|
char const *error_msg =
|
|
parse_with_separator (spec, colon, uid, gid, username, groupname);
|
|
bool warn = false;
|
|
|
|
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))
|
|
{
|
|
warn = true;
|
|
error_msg = pwarn ? N_("warning: '.' should be ':'") : NULL;
|
|
}
|
|
}
|
|
|
|
if (pwarn)
|
|
*pwarn = warn;
|
|
return error_msg;
|
|
}
|
|
|
|
/* Like parse_user_spec_warn, but generate only errors; no warnings. */
|
|
|
|
char const *
|
|
parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
|
|
char **username, char **groupname)
|
|
{
|
|
return parse_user_spec_warn (spec, uid, gid, username, groupname, NULL);
|
|
}
|
|
|
|
#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
|
|
|
|
/*
|
|
Local Variables:
|
|
indent-tabs-mode: nil
|
|
End:
|
|
*/
|