Import 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-2006 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      or EOF.
81    - It's typically faster.
82    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
83    isdigit 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 = N_("invalid spec");
113
114   const char *error_msg;
115   struct passwd *pwd;
116   struct group *grp;
117   char *u;
118   char const *g;
119   char *gname = NULL;
120   uid_t unum = *uid;
121   gid_t gnum = *gid;
122
123   error_msg = NULL;
124   *username = *groupname = NULL;
125
126   /* Set U and G to nonzero length strings corresponding to user and
127      group specifiers or to NULL.  If U is not NULL, it is a newly
128      allocated string.  */
129
130   u = NULL;
131   if (separator == NULL)
132     {
133       if (*spec)
134         u = xstrdup (spec);
135     }
136   else
137     {
138       size_t ulen = separator - spec;
139       if (ulen != 0)
140         {
141           u = xmemdup (spec, ulen + 1);
142           u[ulen] = '\0';
143         }
144     }
145
146   g = (separator == NULL || *(separator + 1) == '\0'
147        ? NULL
148        : separator + 1);
149
150 #ifdef __DJGPP__
151   /* Pretend that we are the user U whose group is G.  This makes
152      pwd and grp functions ``know'' about the UID and GID of these.  */
153   if (u && !is_number (u))
154     setenv ("USER", u, 1);
155   if (g && !is_number (g))
156     setenv ("GROUP", g, 1);
157 #endif
158
159   if (u != NULL)
160     {
161       pwd = 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)
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       grp = getgrnam (g);
202       if (grp == NULL)
203         {
204           unsigned long int tmp;
205           if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK && tmp <= MAXGID)
206             gnum = tmp;
207           else
208             error_msg = E_invalid_group;
209         }
210       else
211         gnum = grp->gr_gid;
212       endgrent ();              /* Save a file descriptor.  */
213       gname = xstrdup (g);
214     }
215
216   if (error_msg == NULL)
217     {
218       *uid = unum;
219       *gid = gnum;
220       *username = u;
221       *groupname = gname;
222       u = NULL;
223     }
224   else
225     free (gname);
226
227   free (u);
228   return _(error_msg);
229 }
230
231 /* Extract from SPEC, which has the form "[user][:.][group]",
232    a USERNAME, UID U, GROUPNAME, and GID G.
233    Either user or group, or both, must be present.
234    If the group is omitted but the separator is given,
235    use the given user's login group.
236    If SPEC contains a `:', then use that as the separator, ignoring
237    any `.'s.  If there is no `:', but there is a `.', then first look
238    up the entire SPEC as a login name.  If that look-up fails, then
239    try again interpreting the `.'  as a separator.
240
241    USERNAME and GROUPNAME will be in newly malloc'd memory.
242    Either one might be NULL instead, indicating that it was not
243    given and the corresponding numeric ID was left unchanged.
244
245    Return NULL if successful, a static error message string if not.  */
246
247 char const *
248 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
249                  char **username, char **groupname)
250 {
251   char const *colon = strchr (spec, ':');
252   char const *error_msg =
253     parse_with_separator (spec, colon, uid, gid, username, groupname);
254
255   if (!colon && error_msg)
256     {
257       /* If there's no colon but there is a dot, and if looking up the
258          whole spec failed (i.e., the spec is not a owner name that
259          includes a dot), then try again, but interpret the dot as a
260          separator.  This is a compatible extension to POSIX, since
261          the POSIX-required behavior is always tried first.  */
262
263       char const *dot = strchr (spec, '.');
264       if (dot
265           && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
266         error_msg = NULL;
267     }
268
269   return error_msg;
270 }
271
272 #ifdef TEST
273
274 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
275
276 int
277 main (int argc, char **argv)
278 {
279   int i;
280
281   for (i = 1; i < argc; i++)
282     {
283       const char *e;
284       char *username, *groupname;
285       uid_t uid;
286       gid_t gid;
287       char *tmp;
288
289       tmp = strdup (argv[i]);
290       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
291       free (tmp);
292       printf ("%s: %lu %lu %s %s %s\n",
293               argv[i],
294               (unsigned long int) uid,
295               (unsigned long int) gid,
296               NULL_CHECK (username),
297               NULL_CHECK (groupname),
298               NULL_CHECK (e));
299     }
300
301   exit (0);
302 }
303
304 #endif