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