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