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