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