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