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