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