maint: update copyright
[gnulib.git] / lib / userspec.c
1 /* userspec.c -- Parse a user and group string.
2    Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2014 Free Software
3    Foundation, Inc.
4
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.
9
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.
14
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/>.  */
17
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
19
20 #include <config.h>
21
22 /* Specification.  */
23 #include "userspec.h"
24
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <pwd.h>
29 #include <grp.h>
30
31 #if HAVE_SYS_PARAM_H
32 # include <sys/param.h>
33 #endif
34
35 #include <limits.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <unistd.h>
40
41 #include "intprops.h"
42 #include "inttostr.h"
43 #include "xalloc.h"
44 #include "xstrtol.h"
45
46 #include "gettext.h"
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
49
50 #ifndef HAVE_ENDGRENT
51 # define endgrent() ((void) 0)
52 #endif
53
54 #ifndef HAVE_ENDPWENT
55 # define endpwent() ((void) 0)
56 #endif
57
58 #ifndef UID_T_MAX
59 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
60 #endif
61
62 #ifndef GID_T_MAX
63 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
64 #endif
65
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
73
74 #ifdef __DJGPP__
75
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)
84
85 /* Return true if STR represents an unsigned decimal integer.  */
86
87 static bool
88 is_number (const char *str)
89 {
90   do
91     {
92       if (!ISDIGIT (*str))
93         return false;
94     }
95   while (*++str);
96
97   return true;
98 }
99 #endif
100
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)
105 {
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");
109
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;
118
119   error_msg = NULL;
120   if (username)
121     *username = NULL;
122   if (groupname)
123     *groupname = NULL;
124
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.  */
128
129   u = NULL;
130   if (separator == NULL)
131     {
132       if (*spec)
133         u = xstrdup (spec);
134     }
135   else
136     {
137       size_t ulen = separator - spec;
138       if (ulen != 0)
139         {
140           u = xmemdup (spec, ulen + 1);
141           u[ulen] = '\0';
142         }
143     }
144
145   g = (separator == NULL || *(separator + 1) == '\0'
146        ? NULL
147        : separator + 1);
148
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
157
158   if (u != NULL)
159     {
160       /* If it starts with "+", skip the look-up.  */
161       pwd = (*u == '+' ? NULL : getpwnam (u));
162       if (pwd == NULL)
163         {
164           bool use_login_group = (separator != NULL && g == NULL);
165           if (use_login_group)
166             {
167               /* If there is no group,
168                  then there may not be a trailing ":", either.  */
169               error_msg = E_bad_spec;
170             }
171           else
172             {
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;
179             }
180         }
181       else
182         {
183           unum = pwd->pw_uid;
184           if (g == NULL && separator != NULL)
185             {
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 ();
193             }
194         }
195       endpwent ();
196     }
197
198   if (g != NULL && error_msg == NULL)
199     {
200       /* Explicit group.  */
201       /* If it starts with "+", skip the look-up.  */
202       grp = (*g == '+' ? NULL : getgrnam (g));
203       if (grp == NULL)
204         {
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;
211         }
212       else
213         gnum = grp->gr_gid;
214       endgrent ();              /* Save a file descriptor.  */
215       gname = xstrdup (g);
216     }
217
218   if (error_msg == NULL)
219     {
220       *uid = unum;
221       if (gid)
222         *gid = gnum;
223       if (username)
224         {
225           *username = u;
226           u = NULL;
227         }
228       if (groupname)
229         {
230           *groupname = gname;
231           gname = NULL;
232         }
233     }
234
235   free (u);
236   free (gname);
237   return _(error_msg);
238 }
239
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.
251
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.
255
256    Return NULL if successful, a static error message string if not.  */
257
258 char const *
259 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
260                  char **username, char **groupname)
261 {
262   char const *colon = gid ? strchr (spec, ':') : NULL;
263   char const *error_msg =
264     parse_with_separator (spec, colon, uid, gid, username, groupname);
265
266   if (gid && !colon && error_msg)
267     {
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.  */
273
274       char const *dot = strchr (spec, '.');
275       if (dot
276           && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
277         error_msg = NULL;
278     }
279
280   return error_msg;
281 }
282
283 #ifdef TEST
284
285 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
286
287 int
288 main (int argc, char **argv)
289 {
290   int i;
291
292   for (i = 1; i < argc; i++)
293     {
294       const char *e;
295       char *username, *groupname;
296       uid_t uid;
297       gid_t gid;
298       char *tmp;
299
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));
310     }
311
312   exit (0);
313 }
314
315 #endif
316
317 /*
318 Local Variables:
319 indent-tabs-mode: nil
320 End:
321 */