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