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