1 /* userspec.c -- Parse a user and group string.
2 Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2005 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 2, or (at your option)
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, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
30 #include <sys/types.h>
35 # include <sys/param.h>
53 #define _(msgid) gettext (msgid)
54 #define N_(msgid) msgid
56 #ifndef _POSIX_VERSION
57 struct passwd *getpwnam ();
58 struct group *getgrnam ();
59 struct group *getgrgid ();
63 # define endgrent() ((void) 0)
67 # define endpwent() ((void) 0)
71 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
75 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
78 /* MAXUID may come from limits.h or sys/params.h. */
80 # define MAXUID UID_T_MAX
83 # define MAXGID GID_T_MAX
86 /* ISDIGIT differs from isdigit, as follows:
87 - Its arg may be any int or unsigned int; it need not be an unsigned char.
88 - It's guaranteed to evaluate its argument exactly once.
89 - It's typically faster.
90 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
91 ISDIGIT_LOCALE unless it's important to use the locale's definition
92 of `digit' even when the host does not conform to POSIX. */
93 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
97 /* Return true if STR represents an unsigned decimal integer. */
100 is_number (const char *str)
114 parse_with_separator (char const *spec, char const *separator,
115 uid_t *uid, gid_t *gid,
116 char **username, char **groupname)
118 static const char *E_invalid_user = N_("invalid user");
119 static const char *E_invalid_group = N_("invalid group");
120 static const char *E_bad_spec =
121 N_("cannot get the login group of a numeric UID");
123 const char *error_msg;
133 *username = *groupname = NULL;
135 /* Set U and G to nonzero length strings corresponding to user and
136 group specifiers or to NULL. If U is not NULL, it is a newly
140 if (separator == NULL)
147 size_t ulen = separator - spec;
150 u = xmemdup (spec, ulen + 1);
155 g = (separator == NULL || *(separator + 1) == '\0'
160 /* Pretend that we are the user U whose group is G. This makes
161 pwd and grp functions ``know'' about the UID and GID of these. */
162 if (u && !is_number (u))
163 setenv ("USER", u, 1);
164 if (g && !is_number (g))
165 setenv ("GROUP", g, 1);
173 bool use_login_group = (separator != NULL && g == NULL);
175 error_msg = E_bad_spec;
178 unsigned long int tmp;
179 if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
183 error_msg = E_invalid_user;
189 if (g == NULL && separator != NULL)
191 /* A separator was given, but a group was not specified,
192 so get the login group. */
193 char buf[INT_BUFSIZE_BOUND (uintmax_t)];
195 grp = getgrgid (gnum);
196 gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
203 if (g != NULL && error_msg == NULL)
205 /* Explicit group. */
209 unsigned long int tmp;
210 if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK && tmp <= MAXGID)
213 error_msg = E_invalid_group;
217 endgrent (); /* Save a file descriptor. */
221 if (error_msg == NULL)
236 /* Extract from SPEC, which has the form "[user][:.][group]",
237 a USERNAME, UID U, GROUPNAME, and GID G.
238 Either user or group, or both, must be present.
239 If the group is omitted but the separator is given,
240 use the given user's login group.
241 If SPEC contains a `:', then use that as the separator, ignoring
242 any `.'s. If there is no `:', but there is a `.', then first look
243 up the entire SPEC as a login name. If that look-up fails, then
244 try again interpreting the `.' as a separator.
246 USERNAME and GROUPNAME will be in newly malloc'd memory.
247 Either one might be NULL instead, indicating that it was not
248 given and the corresponding numeric ID was left unchanged.
250 Return NULL if successful, a static error message string if not. */
253 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
254 char **username, char **groupname)
256 char const *colon = strchr (spec, ':');
257 char const *error_msg =
258 parse_with_separator (spec, colon, uid, gid, username, groupname);
260 if (!colon && error_msg)
262 /* If there's no colon but there is a dot, and if looking up the
263 whole spec failed (i.e., the spec is not a owner name that
264 includes a dot), then try again, but interpret the dot as a
265 separator. This is a compatible extension to POSIX, since
266 the POSIX-required behavior is always tried first. */
268 char const *dot = strchr (spec, '.');
270 && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
279 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
282 main (int argc, char **argv)
286 for (i = 1; i < argc; i++)
289 char *username, *groupname;
294 tmp = strdup (argv[i]);
295 e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
297 printf ("%s: %lu %lu %s %s %s\n",
299 (unsigned long int) uid,
300 (unsigned long int) gid,
301 NULL_CHECK (username),
302 NULL_CHECK (groupname),