malloc-h: New module.
[gnulib.git] / lib / userspec.c
blobcaaad082ad82dd61bc2d658fa5ae50faa9b6db28
1 /* userspec.c -- Parse a user and group string.
2 Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2020 Free Software
3 Foundation, Inc.
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 <https://www.gnu.org/licenses/>. */
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
20 #include <config.h>
22 /* Specification. */
23 #include "userspec.h"
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <pwd.h>
29 #include <grp.h>
31 #if HAVE_SYS_PARAM_H
32 # include <sys/param.h>
33 #endif
35 #include <limits.h>
36 #include <stdlib.h>
37 #include <string.h>
39 #include <unistd.h>
41 #include "intprops.h"
42 #include "inttostr.h"
43 #include "xalloc.h"
44 #include "xstrtol.h"
46 #include "gettext.h"
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
50 #ifndef HAVE_ENDGRENT
51 # define endgrent() ((void) 0)
52 #endif
54 #ifndef HAVE_ENDPWENT
55 # define endpwent() ((void) 0)
56 #endif
58 #ifndef UID_T_MAX
59 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
60 #endif
62 #ifndef GID_T_MAX
63 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
64 #endif
66 /* MAXUID may come from limits.h or sys/params.h. */
67 #ifndef MAXUID
68 # define MAXUID UID_T_MAX
69 #endif
70 #ifndef MAXGID
71 # define MAXGID GID_T_MAX
72 #endif
74 #ifdef __DJGPP__
76 /* ISDIGIT differs from isdigit, as follows:
77 - Its arg may be any int or unsigned int; it need not be an unsigned char
78 or EOF.
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. */
87 static bool
88 is_number (const char *str)
92 if (!ISDIGIT (*str))
93 return false;
95 while (*++str);
97 return true;
99 #endif
101 static char const *
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;
111 struct passwd *pwd;
112 struct group *grp;
113 char *u;
114 char const *g;
115 char *gname = NULL;
116 uid_t unum = *uid;
117 gid_t gnum = gid ? *gid : -1;
119 error_msg = NULL;
120 if (username)
121 *username = NULL;
122 if (groupname)
123 *groupname = NULL;
125 /* Set U and G to nonzero length strings corresponding to user and
126 group specifiers or to NULL. If U is not NULL, it is a newly
127 allocated string. */
129 u = NULL;
130 if (separator == NULL)
132 if (*spec)
133 u = xstrdup (spec);
135 else
137 size_t ulen = separator - spec;
138 if (ulen != 0)
140 u = xmemdup (spec, ulen + 1);
141 u[ulen] = '\0';
145 g = (separator == NULL || *(separator + 1) == '\0'
146 ? NULL
147 : separator + 1);
149 #ifdef __DJGPP__
150 /* Pretend that we are the user U whose group is G. This makes
151 pwd and grp functions "know" about the UID and GID of these. */
152 if (u && !is_number (u))
153 setenv ("USER", u, 1);
154 if (g && !is_number (g))
155 setenv ("GROUP", g, 1);
156 #endif
158 if (u != NULL)
160 /* If it starts with "+", skip the look-up. */
161 pwd = (*u == '+' ? NULL : getpwnam (u));
162 if (pwd == NULL)
164 bool use_login_group = (separator != NULL && g == NULL);
165 if (use_login_group)
167 /* If there is no group,
168 then there may not be a trailing ":", either. */
169 error_msg = E_bad_spec;
171 else
173 unsigned long int tmp;
174 if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
175 && tmp <= MAXUID && (uid_t) tmp != (uid_t) -1)
176 unum = tmp;
177 else
178 error_msg = E_invalid_user;
181 else
183 unum = pwd->pw_uid;
184 if (g == NULL && separator != NULL)
186 /* A separator was given, but a group was not specified,
187 so get the login group. */
188 char buf[INT_BUFSIZE_BOUND (uintmax_t)];
189 gnum = pwd->pw_gid;
190 grp = getgrgid (gnum);
191 gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
192 endgrent ();
195 endpwent ();
198 if (g != NULL && error_msg == NULL)
200 /* Explicit group. */
201 /* If it starts with "+", skip the look-up. */
202 grp = (*g == '+' ? NULL : getgrnam (g));
203 if (grp == NULL)
205 unsigned long int tmp;
206 if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK
207 && tmp <= MAXGID && (gid_t) tmp != (gid_t) -1)
208 gnum = tmp;
209 else
210 error_msg = E_invalid_group;
212 else
213 gnum = grp->gr_gid;
214 endgrent (); /* Save a file descriptor. */
215 gname = xstrdup (g);
218 if (error_msg == NULL)
220 *uid = unum;
221 if (gid)
222 *gid = gnum;
223 if (username)
225 *username = u;
226 u = NULL;
228 if (groupname)
230 *groupname = gname;
231 gname = NULL;
235 free (u);
236 free (gname);
237 return error_msg ? _(error_msg) : NULL;
240 /* Extract from SPEC, which has the form "[user][:.][group]",
241 a USERNAME, UID U, GROUPNAME, and GID G.
242 If the GID parameter is NULL the entire SPEC is treated as a user.
243 If the USERNAME and GROUPNAME parameters are NULL they're ignored.
244 Either user or group, or both, must be present.
245 If the group is omitted but the separator is given,
246 use the given user's login group.
247 If SPEC contains a ':', then use that as the separator, ignoring
248 any '.'s. If there is no ':', but there is a '.', then first look
249 up the entire SPEC as a login name. If that look-up fails, then
250 try again interpreting the '.' as a separator.
252 USERNAME and GROUPNAME will be in newly malloc'd memory.
253 Either one might be NULL instead, indicating that it was not
254 given and the corresponding numeric ID was left unchanged.
256 Return NULL if successful, a static error message string if not. */
258 char const *
259 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
260 char **username, char **groupname)
262 char const *colon = gid ? strchr (spec, ':') : NULL;
263 char const *error_msg =
264 parse_with_separator (spec, colon, uid, gid, username, groupname);
266 if (gid && !colon && error_msg)
268 /* If there's no colon but there is a dot, and if looking up the
269 whole spec failed (i.e., the spec is not an owner name that
270 includes a dot), then try again, but interpret the dot as a
271 separator. This is a compatible extension to POSIX, since
272 the POSIX-required behavior is always tried first. */
274 char const *dot = strchr (spec, '.');
275 if (dot
276 && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
277 error_msg = NULL;
280 return error_msg;
283 #ifdef TEST
285 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
288 main (int argc, char **argv)
290 int i;
292 for (i = 1; i < argc; i++)
294 const char *e;
295 char *username, *groupname;
296 uid_t uid;
297 gid_t gid;
298 char *tmp;
300 tmp = strdup (argv[i]);
301 e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
302 free (tmp);
303 printf ("%s: %lu %lu %s %s %s\n",
304 argv[i],
305 (unsigned long int) uid,
306 (unsigned long int) gid,
307 NULL_CHECK (username),
308 NULL_CHECK (groupname),
309 NULL_CHECK (e));
312 exit (0);
315 #endif
318 Local Variables:
319 indent-tabs-mode: nil
320 End: