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