| /* userspec.c -- Parse a user and group string. | |
| Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2026 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) dgettext (GNULIB_TEXT_DOMAIN, 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 = 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. */ | |
| char *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'; | |
| } | |
| } | |
| char const *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 | |
| char *gname = NULL; | |
| uid_t unum = *uid; | |
| gid_t gnum = gid ? *gid : -1; | |
| if (u != NULL) | |
| { | |
| /* If it starts with "+", skip the look-up. */ | |
| struct passwd *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; | |
| struct group *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. */ | |
| struct group *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) | |
| { | |
| for (int i = 1; i < argc; i++) | |
| { | |
| char *tmp = strdup (argv[i]); | |
| uid_t uid; | |
| gid_t gid; | |
| char *username; | |
| char *groupname; | |
| const char *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: | |
| */ |