maint: update all copyright year number ranges
[gnulib.git] / lib / userspec.c
1 /* userspec.c -- Parse a user and group string.
2    Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2013 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;
118
119   error_msg = NULL;
120   *username = *groupname = NULL;
121
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
124      allocated string.  */
125
126   u = NULL;
127   if (separator == NULL)
128     {
129       if (*spec)
130         u = xstrdup (spec);
131     }
132   else
133     {
134       size_t ulen = separator - spec;
135       if (ulen != 0)
136         {
137           u = xmemdup (spec, ulen + 1);
138           u[ulen] = '\0';
139         }
140     }
141
142   g = (separator == NULL || *(separator + 1) == '\0'
143        ? NULL
144        : separator + 1);
145
146 #ifdef __DJGPP__
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);
153 #endif
154
155   if (u != NULL)
156     {
157       /* If it starts with "+", skip the look-up.  */
158       pwd = (*u == '+' ? NULL : getpwnam (u));
159       if (pwd == NULL)
160         {
161           bool use_login_group = (separator != NULL && g == NULL);
162           if (use_login_group)
163             {
164               /* If there is no group,
165                  then there may not be a trailing ":", either.  */
166               error_msg = E_bad_spec;
167             }
168           else
169             {
170               unsigned long int tmp;
171               if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
172                   && tmp <= MAXUID && (uid_t) tmp != (uid_t) -1)
173                 unum = tmp;
174               else
175                 error_msg = E_invalid_user;
176             }
177         }
178       else
179         {
180           unum = pwd->pw_uid;
181           if (g == NULL && separator != NULL)
182             {
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)];
186               gnum = pwd->pw_gid;
187               grp = getgrgid (gnum);
188               gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
189               endgrent ();
190             }
191         }
192       endpwent ();
193     }
194
195   if (g != NULL && error_msg == NULL)
196     {
197       /* Explicit group.  */
198       /* If it starts with "+", skip the look-up.  */
199       grp = (*g == '+' ? NULL : getgrnam (g));
200       if (grp == NULL)
201         {
202           unsigned long int tmp;
203           if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK
204               && tmp <= MAXGID && (gid_t) tmp != (gid_t) -1)
205             gnum = tmp;
206           else
207             error_msg = E_invalid_group;
208         }
209       else
210         gnum = grp->gr_gid;
211       endgrent ();              /* Save a file descriptor.  */
212       gname = xstrdup (g);
213     }
214
215   if (error_msg == NULL)
216     {
217       *uid = unum;
218       *gid = gnum;
219       *username = u;
220       *groupname = gname;
221       u = NULL;
222     }
223   else
224     free (gname);
225
226   free (u);
227   return _(error_msg);
228 }
229
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.
239
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.
243
244    Return NULL if successful, a static error message string if not.  */
245
246 char const *
247 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
248                  char **username, char **groupname)
249 {
250   char const *colon = strchr (spec, ':');
251   char const *error_msg =
252     parse_with_separator (spec, colon, uid, gid, username, groupname);
253
254   if (!colon && error_msg)
255     {
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 an 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.  */
261
262       char const *dot = strchr (spec, '.');
263       if (dot
264           && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
265         error_msg = NULL;
266     }
267
268   return error_msg;
269 }
270
271 #ifdef TEST
272
273 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
274
275 int
276 main (int argc, char **argv)
277 {
278   int i;
279
280   for (i = 1; i < argc; i++)
281     {
282       const char *e;
283       char *username, *groupname;
284       uid_t uid;
285       gid_t gid;
286       char *tmp;
287
288       tmp = strdup (argv[i]);
289       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
290       free (tmp);
291       printf ("%s: %lu %lu %s %s %s\n",
292               argv[i],
293               (unsigned long int) uid,
294               (unsigned long int) gid,
295               NULL_CHECK (username),
296               NULL_CHECK (groupname),
297               NULL_CHECK (e));
298     }
299
300   exit (0);
301 }
302
303 #endif
304
305 /*
306 Local Variables:
307 indent-tabs-mode: nil
308 End:
309 */