Sync from coreutils.
[gnulib.git] / lib / userspec.c
1 /* userspec.c -- Parse a user and group string.
2    Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2005 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 2, or (at your option)
8    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, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 /* Specification.  */
26 #include "userspec.h"
27
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <sys/types.h>
31 #include <pwd.h>
32 #include <grp.h>
33
34 #if HAVE_SYS_PARAM_H
35 # include <sys/param.h>
36 #endif
37
38 #include <limits.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #include <unistd.h>
43
44 #include "intprops.h"
45 #include "inttostr.h"
46 #include "strdup.h"
47 #include "xalloc.h"
48 #include "xstrtol.h"
49
50 #include "gettext.h"
51 #define _(msgid) gettext (msgid)
52 #define N_(msgid) msgid
53
54 #ifndef HAVE_ENDGRENT
55 # define endgrent() ((void) 0)
56 #endif
57
58 #ifndef HAVE_ENDPWENT
59 # define endpwent() ((void) 0)
60 #endif
61
62 #ifndef UID_T_MAX
63 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
64 #endif
65
66 #ifndef GID_T_MAX
67 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
68 #endif
69
70 /* MAXUID may come from limits.h or sys/params.h.  */
71 #ifndef MAXUID
72 # define MAXUID UID_T_MAX
73 #endif
74 #ifndef MAXGID
75 # define MAXGID GID_T_MAX
76 #endif
77
78 /* ISDIGIT differs from isdigit, as follows:
79    - Its arg may be any int or unsigned int; it need not be an unsigned char.
80    - It's guaranteed to evaluate its argument exactly once.
81    - It's typically faster.
82    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
83    ISDIGIT_LOCALE unless it's important to use the locale's definition
84    of `digit' even when the host does not conform to POSIX.  */
85 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
86
87 #ifdef __DJGPP__
88
89 /* Return true if STR represents an unsigned decimal integer.  */
90
91 static bool
92 is_number (const char *str)
93 {
94   do
95     {
96       if (!ISDIGIT (*str))
97         return false;
98     }
99   while (*++str);
100
101   return true;
102 }
103 #endif
104
105 static char const *
106 parse_with_separator (char const *spec, char const *separator,
107                       uid_t *uid, gid_t *gid,
108                       char **username, char **groupname)
109 {
110   static const char *E_invalid_user = N_("invalid user");
111   static const char *E_invalid_group = N_("invalid group");
112   static const char *E_bad_spec =
113     N_("cannot get the login group of a numeric UID");
114
115   const char *error_msg;
116   struct passwd *pwd;
117   struct group *grp;
118   char *u;
119   char const *g;
120   char *gname = NULL;
121   uid_t unum = *uid;
122   gid_t gnum = *gid;
123
124   error_msg = NULL;
125   *username = *groupname = NULL;
126
127   /* Set U and G to nonzero length strings corresponding to user and
128      group specifiers or to NULL.  If U is not NULL, it is a newly
129      allocated string.  */
130
131   u = NULL;
132   if (separator == NULL)
133     {
134       if (*spec)
135         u = xstrdup (spec);
136     }
137   else
138     {
139       size_t ulen = separator - spec;
140       if (ulen != 0)
141         {
142           u = xmemdup (spec, ulen + 1);
143           u[ulen] = '\0';
144         }
145     }
146
147   g = (separator == NULL || *(separator + 1) == '\0'
148        ? NULL
149        : separator + 1);
150
151 #ifdef __DJGPP__
152   /* Pretend that we are the user U whose group is G.  This makes
153      pwd and grp functions ``know'' about the UID and GID of these.  */
154   if (u && !is_number (u))
155     setenv ("USER", u, 1);
156   if (g && !is_number (g))
157     setenv ("GROUP", g, 1);
158 #endif
159
160   if (u != NULL)
161     {
162       pwd = getpwnam (u);
163       if (pwd == NULL)
164         {
165           bool use_login_group = (separator != NULL && g == NULL);
166           if (use_login_group)
167             error_msg = E_bad_spec;
168           else
169             {
170               unsigned long int tmp;
171               if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
172                   && tmp <= MAXUID)
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       grp = getgrnam (g);
199       if (grp == NULL)
200         {
201           unsigned long int tmp;
202           if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK && tmp <= MAXGID)
203             gnum = tmp;
204           else
205             error_msg = E_invalid_group;
206         }
207       else
208         gnum = grp->gr_gid;
209       endgrent ();              /* Save a file descriptor.  */
210       gname = xstrdup (g);
211     }
212
213   if (error_msg == NULL)
214     {
215       *uid = unum;
216       *gid = gnum;
217       *username = u;
218       *groupname = gname;
219       u = NULL;
220     }
221   else
222     free (gname);
223
224   free (u);
225   return _(error_msg);
226 }
227
228 /* Extract from SPEC, which has the form "[user][:.][group]",
229    a USERNAME, UID U, GROUPNAME, and GID G.
230    Either user or group, or both, must be present.
231    If the group is omitted but the separator is given,
232    use the given user's login group.
233    If SPEC contains a `:', then use that as the separator, ignoring
234    any `.'s.  If there is no `:', but there is a `.', then first look
235    up the entire SPEC as a login name.  If that look-up fails, then
236    try again interpreting the `.'  as a separator.
237
238    USERNAME and GROUPNAME will be in newly malloc'd memory.
239    Either one might be NULL instead, indicating that it was not
240    given and the corresponding numeric ID was left unchanged.
241
242    Return NULL if successful, a static error message string if not.  */
243
244 char const *
245 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
246                  char **username, char **groupname)
247 {
248   char const *colon = strchr (spec, ':');
249   char const *error_msg =
250     parse_with_separator (spec, colon, uid, gid, username, groupname);
251
252   if (!colon && error_msg)
253     {
254       /* If there's no colon but there is a dot, and if looking up the
255          whole spec failed (i.e., the spec is not a owner name that
256          includes a dot), then try again, but interpret the dot as a
257          separator.  This is a compatible extension to POSIX, since
258          the POSIX-required behavior is always tried first.  */
259
260       char const *dot = strchr (spec, '.');
261       if (dot
262           && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
263         error_msg = NULL;
264     }
265
266   return error_msg;
267 }
268
269 #ifdef TEST
270
271 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
272
273 int
274 main (int argc, char **argv)
275 {
276   int i;
277
278   for (i = 1; i < argc; i++)
279     {
280       const char *e;
281       char *username, *groupname;
282       uid_t uid;
283       gid_t gid;
284       char *tmp;
285
286       tmp = strdup (argv[i]);
287       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
288       free (tmp);
289       printf ("%s: %lu %lu %s %s %s\n",
290               argv[i],
291               (unsigned long int) uid,
292               (unsigned long int) gid,
293               NULL_CHECK (username),
294               NULL_CHECK (groupname),
295               NULL_CHECK (e));
296     }
297
298   exit (0);
299 }
300
301 #endif