6bf40895d92264e852788aeeefb43cc0b3d0eb16
[gnulib.git] / lib / userspec.c
1 /* userspec.c -- Parse a user and group string.
2    Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
20
21 #if HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 /* Specification.  */
26 #include "userspec.h"
27
28 #include <alloca.h>
29
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <sys/types.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #if HAVE_SYS_PARAM_H
37 # include <sys/param.h>
38 #endif
39
40 #include <limits.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44 #if HAVE_UNISTD_H
45 # include <unistd.h>
46 #endif
47
48 #include "inttostr.h"
49 #include "strdup.h"
50 #include "xalloc.h"
51 #include "xstrtol.h"
52
53 #include "gettext.h"
54 #define _(msgid) gettext (msgid)
55 #define N_(msgid) msgid
56
57 #ifndef _POSIX_VERSION
58 struct passwd *getpwnam ();
59 struct group *getgrnam ();
60 struct group *getgrgid ();
61 #endif
62
63 #ifndef HAVE_ENDGRENT
64 # define endgrent() ((void) 0)
65 #endif
66
67 #ifndef HAVE_ENDPWENT
68 # define endpwent() ((void) 0)
69 #endif
70
71 /* The extra casts work around common compiler bugs.  */
72 #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
73 /* The outer cast is needed to work around a bug in Cray C 5.0.3.0.
74    It is necessary at least when t == time_t.  */
75 #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \
76                               ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0))
77 #define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t)))
78
79 #ifndef UID_T_MAX
80 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
81 #endif
82
83 #ifndef GID_T_MAX
84 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
85 #endif
86
87 /* MAXUID may come from limits.h or sys/params.h.  */
88 #ifndef MAXUID
89 # define MAXUID UID_T_MAX
90 #endif
91 #ifndef MAXGID
92 # define MAXGID GID_T_MAX
93 #endif
94
95 /* Perform the equivalent of the statement `dest = strdup (src);',
96    but obtaining storage via alloca instead of from the heap.  */
97
98 #define V_STRDUP(dest, src)                                             \
99   do                                                                    \
100     {                                                                   \
101       size_t size = strlen (src) + 1;                                   \
102       (dest) = (char *) alloca (size);                                  \
103       memcpy (dest, src, size);                                         \
104     }                                                                   \
105   while (0)
106
107 /* ISDIGIT differs from isdigit, as follows:
108    - Its arg may be any int or unsigned int; it need not be an unsigned char.
109    - It's guaranteed to evaluate its argument exactly once.
110    - It's typically faster.
111    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
112    ISDIGIT_LOCALE unless it's important to use the locale's definition
113    of `digit' even when the host does not conform to POSIX.  */
114 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
115
116 #ifdef __DJGPP__
117
118 /* Return true if STR represents an unsigned decimal integer.  */
119
120 static bool
121 is_number (const char *str)
122 {
123   do
124     {
125       if (!ISDIGIT (*str))
126         return false;
127     }
128   while (*++str);
129
130   return true;
131 }
132 #endif
133
134 /* Extract from NAME, which has the form "[user][:.][group]",
135    a USERNAME, UID U, GROUPNAME, and GID G.
136    Either user or group, or both, must be present.
137    If the group is omitted but the ":" separator is given,
138    use the given user's login group.
139    If SPEC_ARG contains a `:', then use that as the separator, ignoring
140    any `.'s.  If there is no `:', but there is a `.', then first look
141    up the entire SPEC_ARG as a login name.  If that look-up fails, then
142    try again interpreting the `.'  as a separator.
143
144    USERNAME and GROUPNAME will be in newly malloc'd memory.
145    Either one might be NULL instead, indicating that it was not
146    given and the corresponding numeric ID was left unchanged.
147
148    Return NULL if successful, a static error message string if not.  */
149
150 const char *
151 parse_user_spec (const char *spec_arg, uid_t *uid, gid_t *gid,
152                  char **username_arg, char **groupname_arg)
153 {
154   static const char *E_invalid_user = N_("invalid user");
155   static const char *E_invalid_group = N_("invalid group");
156   static const char *E_bad_spec =
157     N_("cannot get the login group of a numeric UID");
158   static const char *E_cannot_omit_both =
159     N_("cannot omit both user and group");
160
161   const char *error_msg;
162   char *spec;                   /* A copy we can write on.  */
163   struct passwd *pwd;
164   struct group *grp;
165   char *g, *u, *separator;
166   char *groupname;
167   char *dot = NULL;
168
169   error_msg = NULL;
170   *username_arg = *groupname_arg = NULL;
171   groupname = NULL;
172
173   V_STRDUP (spec, spec_arg);
174
175   /* Find the POSIX `:' separator if there is one.  */
176   separator = strchr (spec, ':');
177
178   /* If there is no colon, then see if there's a `.'.  */
179   if (separator == NULL)
180     {
181       dot = strchr (spec, '.');
182       /* If there's no colon but there is a `.', then first look up the
183          whole spec, in case it's an OWNER name that includes a dot.
184          If that fails, then we'll try again, but interpreting the `.'
185          as a separator.  This is a compatible extension to POSIX, since
186          the POSIX-required behavior is always tried first.  */
187     }
188
189  retry:
190
191   /* Replace separator with a NUL.  */
192   if (separator != NULL)
193     *separator = '\0';
194
195   /* Set U and G to non-zero length strings corresponding to user and
196      group specifiers or to NULL.  */
197   u = (*spec == '\0' ? NULL : spec);
198
199   g = (separator == NULL || *(separator + 1) == '\0'
200        ? NULL
201        : separator + 1);
202
203   if (u == NULL && g == NULL)
204     return _(E_cannot_omit_both);
205
206 #ifdef __DJGPP__
207   /* Pretend that we are the user U whose group is G.  This makes
208      pwd and grp functions ``know'' about the UID and GID of these.  */
209   if (u && !is_number (u))
210     setenv ("USER", u, 1);
211   if (g && !is_number (g))
212     setenv ("GROUP", g, 1);
213 #endif
214
215   if (u != NULL)
216     {
217       pwd = getpwnam (u);
218       if (pwd == NULL)
219         {
220           bool use_login_group = (separator != NULL && g == NULL);
221           if (use_login_group)
222             error_msg = E_bad_spec;
223           else
224             {
225               unsigned long int tmp_long;
226               if (! (xstrtoul (u, NULL, 10, &tmp_long, "") == LONGINT_OK
227                      && tmp_long <= MAXUID))
228                 return _(E_invalid_user);
229               *uid = tmp_long;
230             }
231         }
232       else
233         {
234           *uid = pwd->pw_uid;
235           if (g == NULL && separator != NULL)
236             {
237               /* A separator was given, but a group was not specified,
238                  so get the login group.  */
239               *gid = pwd->pw_gid;
240               grp = getgrgid (pwd->pw_gid);
241               if (grp == NULL)
242                 {
243                   char buf[INT_BUFSIZE_BOUND (uintmax_t)];
244                   char const *num = umaxtostr (pwd->pw_gid, buf);
245                   V_STRDUP (groupname, num);
246                 }
247               else
248                 {
249                   V_STRDUP (groupname, grp->gr_name);
250                 }
251               endgrent ();
252             }
253         }
254       endpwent ();
255     }
256
257   if (g != NULL && error_msg == NULL)
258     {
259       /* Explicit group.  */
260       grp = getgrnam (g);
261       if (grp == NULL)
262         {
263           unsigned long int tmp_long;
264           if (! (xstrtoul (g, NULL, 10, &tmp_long, "") == LONGINT_OK
265                  && tmp_long <= MAXGID))
266             return _(E_invalid_group);
267           *gid = tmp_long;
268         }
269       else
270         *gid = grp->gr_gid;
271       endgrent ();              /* Save a file descriptor.  */
272
273       if (error_msg == NULL)
274         V_STRDUP (groupname, g);
275     }
276
277   if (error_msg == NULL)
278     {
279       if (u != NULL)
280         *username_arg = xstrdup (u);
281
282       if (groupname != NULL)
283         *groupname_arg = xstrdup (groupname);
284     }
285
286   if (error_msg && dot)
287     {
288       separator = dot;
289       dot = NULL;
290       error_msg = NULL;
291       goto retry;
292     }
293
294   return _(error_msg);
295 }
296
297 #ifdef TEST
298
299 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
300
301 int
302 main (int argc, char **argv)
303 {
304   int i;
305
306   for (i = 1; i < argc; i++)
307     {
308       const char *e;
309       char *username, *groupname;
310       uid_t uid;
311       gid_t gid;
312       char *tmp;
313
314       tmp = strdup (argv[i]);
315       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
316       free (tmp);
317       printf ("%s: %lu %lu %s %s %s\n",
318               argv[i],
319               (unsigned long int) uid,
320               (unsigned long int) gid,
321               NULL_CHECK (username),
322               NULL_CHECK (groupname),
323               NULL_CHECK (e));
324     }
325
326   exit (0);
327 }
328
329 #endif