Use a consistent style for including <config.h>.
[gnulib.git] / lib / userspec.c
1 /* userspec.c -- Parse a user and group string.
2    Copyright (C) 1989-1992, 1997-1998, 2000, 2002-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 /* Specification.  */
26 #include "userspec.h"
27
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <sys/types.h>
31 #include <pwd.h>
32 #include <grp.h>
33
34 #if HAVE_SYS_PARAM_H
35 # include <sys/param.h>
36 #endif
37
38 #include <limits.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #if HAVE_UNISTD_H
43 # include <unistd.h>
44 #endif
45
46 #include "intprops.h"
47 #include "inttostr.h"
48 #include "strdup.h"
49 #include "xalloc.h"
50 #include "xstrtol.h"
51
52 #include "gettext.h"
53 #define _(msgid) gettext (msgid)
54 #define N_(msgid) msgid
55
56 #ifndef HAVE_ENDGRENT
57 # define endgrent() ((void) 0)
58 #endif
59
60 #ifndef HAVE_ENDPWENT
61 # define endpwent() ((void) 0)
62 #endif
63
64 #ifndef UID_T_MAX
65 # define UID_T_MAX TYPE_MAXIMUM (uid_t)
66 #endif
67
68 #ifndef GID_T_MAX
69 # define GID_T_MAX TYPE_MAXIMUM (gid_t)
70 #endif
71
72 /* MAXUID may come from limits.h or sys/params.h.  */
73 #ifndef MAXUID
74 # define MAXUID UID_T_MAX
75 #endif
76 #ifndef MAXGID
77 # define MAXGID GID_T_MAX
78 #endif
79
80 /* ISDIGIT differs from isdigit, as follows:
81    - Its arg may be any int or unsigned int; it need not be an unsigned char.
82    - It's guaranteed to evaluate its argument exactly once.
83    - It's typically faster.
84    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
85    ISDIGIT_LOCALE unless it's important to use the locale's definition
86    of `digit' even when the host does not conform to POSIX.  */
87 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
88
89 #ifdef __DJGPP__
90
91 /* Return true if STR represents an unsigned decimal integer.  */
92
93 static bool
94 is_number (const char *str)
95 {
96   do
97     {
98       if (!ISDIGIT (*str))
99         return false;
100     }
101   while (*++str);
102
103   return true;
104 }
105 #endif
106
107 static char const *
108 parse_with_separator (char const *spec, char const *separator,
109                       uid_t *uid, gid_t *gid,
110                       char **username, char **groupname)
111 {
112   static const char *E_invalid_user = N_("invalid user");
113   static const char *E_invalid_group = N_("invalid group");
114   static const char *E_bad_spec =
115     N_("cannot get the login group of a numeric UID");
116
117   const char *error_msg;
118   struct passwd *pwd;
119   struct group *grp;
120   char *u;
121   char const *g;
122   char *gname = NULL;
123   uid_t unum = *uid;
124   gid_t gnum = *gid;
125
126   error_msg = NULL;
127   *username = *groupname = NULL;
128
129   /* Set U and G to nonzero length strings corresponding to user and
130      group specifiers or to NULL.  If U is not NULL, it is a newly
131      allocated string.  */
132
133   u = NULL;
134   if (separator == NULL)
135     {
136       if (*spec)
137         u = xstrdup (spec);
138     }
139   else
140     {
141       size_t ulen = separator - spec;
142       if (ulen != 0)
143         {
144           u = xmemdup (spec, ulen + 1);
145           u[ulen] = '\0';
146         }
147     }
148
149   g = (separator == NULL || *(separator + 1) == '\0'
150        ? NULL
151        : separator + 1);
152
153 #ifdef __DJGPP__
154   /* Pretend that we are the user U whose group is G.  This makes
155      pwd and grp functions ``know'' about the UID and GID of these.  */
156   if (u && !is_number (u))
157     setenv ("USER", u, 1);
158   if (g && !is_number (g))
159     setenv ("GROUP", g, 1);
160 #endif
161
162   if (u != NULL)
163     {
164       pwd = getpwnam (u);
165       if (pwd == NULL)
166         {
167           bool use_login_group = (separator != NULL && g == NULL);
168           if (use_login_group)
169             error_msg = E_bad_spec;
170           else
171             {
172               unsigned long int tmp;
173               if (xstrtoul (u, NULL, 10, &tmp, "") == LONGINT_OK
174                   && tmp <= MAXUID)
175                 unum = tmp;
176               else
177                 error_msg = E_invalid_user;
178             }
179         }
180       else
181         {
182           unum = pwd->pw_uid;
183           if (g == NULL && separator != NULL)
184             {
185               /* A separator was given, but a group was not specified,
186                  so get the login group.  */
187               char buf[INT_BUFSIZE_BOUND (uintmax_t)];
188               gnum = pwd->pw_gid;
189               grp = getgrgid (gnum);
190               gname = xstrdup (grp ? grp->gr_name : umaxtostr (gnum, buf));
191               endgrent ();
192             }
193         }
194       endpwent ();
195     }
196
197   if (g != NULL && error_msg == NULL)
198     {
199       /* Explicit group.  */
200       grp = getgrnam (g);
201       if (grp == NULL)
202         {
203           unsigned long int tmp;
204           if (xstrtoul (g, NULL, 10, &tmp, "") == LONGINT_OK && tmp <= MAXGID)
205             gnum = tmp;
206           else
207             error_msg = E_invalid_group;
208         }
209       else
210         gnum = grp->gr_gid;
211       endgrent ();              /* Save a file descriptor.  */
212       gname = xstrdup (g);
213     }
214
215   if (error_msg == NULL)
216     {
217       *uid = unum;
218       *gid = gnum;
219       *username = u;
220       *groupname = gname;
221       u = NULL;
222     }
223   else
224     free (gname);
225
226   free (u);
227   return _(error_msg);
228 }
229
230 /* Extract from SPEC, which has the form "[user][:.][group]",
231    a USERNAME, UID U, GROUPNAME, and GID G.
232    Either user or group, or both, must be present.
233    If the group is omitted but the separator is given,
234    use the given user's login group.
235    If SPEC contains a `:', then use that as the separator, ignoring
236    any `.'s.  If there is no `:', but there is a `.', then first look
237    up the entire SPEC as a login name.  If that look-up fails, then
238    try again interpreting the `.'  as a separator.
239
240    USERNAME and GROUPNAME will be in newly malloc'd memory.
241    Either one might be NULL instead, indicating that it was not
242    given and the corresponding numeric ID was left unchanged.
243
244    Return NULL if successful, a static error message string if not.  */
245
246 char const *
247 parse_user_spec (char const *spec, uid_t *uid, gid_t *gid,
248                  char **username, char **groupname)
249 {
250   char const *colon = strchr (spec, ':');
251   char const *error_msg =
252     parse_with_separator (spec, colon, uid, gid, username, groupname);
253
254   if (!colon && error_msg)
255     {
256       /* If there's no colon but there is a dot, and if looking up the
257          whole spec failed (i.e., the spec is not a owner name that
258          includes a dot), then try again, but interpret the dot as a
259          separator.  This is a compatible extension to POSIX, since
260          the POSIX-required behavior is always tried first.  */
261
262       char const *dot = strchr (spec, '.');
263       if (dot
264           && ! parse_with_separator (spec, dot, uid, gid, username, groupname))
265         error_msg = NULL;
266     }
267
268   return error_msg;
269 }
270
271 #ifdef TEST
272
273 # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
274
275 int
276 main (int argc, char **argv)
277 {
278   int i;
279
280   for (i = 1; i < argc; i++)
281     {
282       const char *e;
283       char *username, *groupname;
284       uid_t uid;
285       gid_t gid;
286       char *tmp;
287
288       tmp = strdup (argv[i]);
289       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
290       free (tmp);
291       printf ("%s: %lu %lu %s %s %s\n",
292               argv[i],
293               (unsigned long int) uid,
294               (unsigned long int) gid,
295               NULL_CHECK (username),
296               NULL_CHECK (groupname),
297               NULL_CHECK (e));
298     }
299
300   exit (0);
301 }
302
303 #endif