1 /* userspec.c -- Parse a user and group string.
2 Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2010 Free Software
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
27 #include <sys/types.h>
32 # include <sys/param.h>
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
51 # define endgrent() ((void) 0)
55 # define endpwent() ((void) 0)
59 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
63 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
66 /* MAXUID may come from limits.h or sys/params.h. */
68 # define MAXUID UID_T_MAX
71 # define MAXGID GID_T_MAX
76 /* ISDIGIT differs from isdigit, as follows:
77 - Its arg may be any int or unsigned int; it need not be an unsigned char
79 - It's typically faster.
80 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
81 isdigit unless it's important to use the locale's definition
82 of `digit' even when the host does not conform to POSIX. */
83 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
85 /* Return true if STR represents an unsigned decimal integer. */
88 is_number (const char *str)
102 parse_with_separator (char const *spec, char const *separator,
103 uid_t *uid, gid_t *gid,
104 char **username, char **groupname)
106 static const char *E_invalid_user = N_("invalid user");
107 static const char *E_invalid_group = N_("invalid group");
108 static const char *E_bad_spec = N_("invalid spec");
110 const char *error_msg;
120 *username = *groupname = NULL;
122 /* Set U and G to nonzero length strings corresponding to user and
123 group specifiers or to NULL. If U is not NULL, it is a newly
127 if (separator == NULL)
134 size_t ulen = separator - spec;
137 u = xmemdup (spec, ulen + 1);
142 g = (separator == NULL || *(separator + 1) == '\0'
147 /* Pretend that we are the user U whose group is G. This makes
148 pwd and grp functions ``know'' about the UID and GID of these. */
149 if (u && !is_number (u))
150 setenv ("USER", u, 1);
151 if (g && !is_number (g))
152 setenv ("GROUP", g, 1);
157 /* If it starts with "+", skip the look-up. */
158 pwd = (*u == '+' ? NULL : getpwnam (u));
161 bool use_login_group = (separator != NULL && g == NULL);
164 /* If there is no group,
165 then there may not be a trailing ":", either. */
166 error_msg = E_bad_spec;
170 unsigned long int tmp;
171 if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
172 && tmp <= MAXUID && (uid_t) tmp != (uid_t) -1)
175 error_msg = E_invalid_user;
181 if (g == NULL && separator != NULL)
183 /* A separator was given, but a group was not specified,
184 so get the login group. */
185 char buf[INT_BUFSIZE_BOUND (uintmax_t)];
187 grp = getgrgid (gnum);
188 gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
195 if (g != NULL && error_msg == NULL)
197 /* Explicit group. */
198 /* If it starts with "+", skip the look-up. */
199 grp = (*g == '+' ? NULL : getgrnam (g));
202 unsigned long int tmp;
203 if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK
204 && tmp <= MAXGID && (gid_t) tmp != (gid_t) -1)
207 error_msg = E_invalid_group;
211 endgrent (); /* Save a file descriptor. */
215 if (error_msg == NULL)
230 /* Extract from SPEC, which has the form "[user][:.][group]",
231 a USERNAME, UID U, GROUPNAME, and GID G.
232 Either user or group, or both, must be present.
233 If the group is omitted but the separator is given,
234 use the given user's login group.
235 If SPEC contains a `:', then use that as the separator, ignoring
236 any `.'s. If there is no `:', but there is a `.', then first look
237 up the entire SPEC as a login name. If that look-up fails, then
238 try again interpreting the `.' as a separator.
240 USERNAME and GROUPNAME will be in newly malloc'd memory.
241 Either one might be NULL instead, indicating that it was not
242 given and the corresponding numeric ID was left unchanged.
244 Return NULL if successful, a static error message string if not. */
247 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
248 char **username, char **groupname)
250 char const *colon = strchr (spec, ':');
251 char const *error_msg =
252 parse_with_separator (spec, colon, uid, gid, username, groupname);
254 if (!colon && error_msg)
256 /* If there's no colon but there is a dot, and if looking up the
257 whole spec failed (i.e., the spec is not a owner name that
258 includes a dot), then try again, but interpret the dot as a
259 separator. This is a compatible extension to POSIX, since
260 the POSIX-required behavior is always tried first. */
262 char const *dot = strchr (spec, '.');
264 && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
273 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
276 main (int argc, char **argv)
280 for (i = 1; i < argc; i++)
283 char *username, *groupname;
288 tmp = strdup (argv[i]);
289 e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
291 printf ("%s: %lu %lu %s %s %s\n",
293 (unsigned long int) uid,
294 (unsigned long int) gid,
295 NULL_CHECK (username),
296 NULL_CHECK (groupname),
307 indent-tabs-mode: nil