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