1 /* userspec.c -- Parse a user and group string.
2    Copyright (C) 1989-1992, 2001, 2004-2005, 2007, 2010, 2014-2015, 2017
3    Free Software 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, 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
16    License along with this program; if not, write to the Free
17    Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301 USA.  */
19 
20 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
21 
22 #include <system.h>
23 #include <alloca.h>
24 #include <stdio.h>
25 #include <ctype.h>
26 #include <sys/types.h>
27 
28 #ifndef HAVE_ENDPWENT
29 # define endpwent()
30 #endif
31 #ifndef HAVE_ENDGRENT
32 # define endgrent()
33 #endif
34 
35 /* Perform the equivalent of the statement `dest = strdup (src);',
36    but obtaining storage via alloca instead of from the heap.  */
37 
38 #define V_STRDUP(dest, src)						\
39   do									\
40     {									\
41       int _len = strlen ((src));					\
42       (dest) = (char *) alloca (_len + 1);				\
43       strcpy (dest, src);						\
44     }									\
45   while (0)
46 
47 /* Return nonzero if STR represents an unsigned decimal integer,
48    otherwise return 0. */
49 
50 static int
isnumber_p(const char * str)51 isnumber_p (const char *str)
52 {
53   for (; *str; str++)
54     if (!isdigit (*str))
55       return 0;
56   return 1;
57 }
58 
59 /* Extract from NAME, which has the form "[user][:.][group]",
60    a USERNAME, UID U, GROUPNAME, and GID G.
61    Either user or group, or both, must be present.
62    If the group is omitted but the ":" or "." separator is given,
63    use the given user's login group.
64 
65    USERNAME and GROUPNAME will be in newly malloc'd memory.
66    Either one might be NULL instead, indicating that it was not
67    given and the corresponding numeric ID was left unchanged.
68 
69    Return NULL if successful, a static error message string if not.  */
70 
71 const char *
parse_user_spec(const char * spec_arg,uid_t * uid,gid_t * gid,char ** username_arg,char ** groupname_arg)72 parse_user_spec (const char *spec_arg, uid_t *uid, gid_t *gid,
73 		 char **username_arg, char **groupname_arg)
74 {
75   static const char *tired = "virtual memory exhausted";
76   const char *error_msg;
77   char *spec;			/* A copy we can write on.  */
78   struct passwd *pwd;
79   struct group *grp;
80   char *g, *u, *separator;
81   char *groupname;
82 
83   error_msg = NULL;
84   *username_arg = *groupname_arg = NULL;
85   groupname = NULL;
86 
87   V_STRDUP (spec, spec_arg);
88 
89   /* Find the separator if there is one.  */
90   separator = strchr (spec, ':');
91   if (separator == NULL)
92     separator = strchr (spec, '.');
93 
94   /* Replace separator with a NUL.  */
95   if (separator != NULL)
96     *separator = '\0';
97 
98   /* Set U and G to non-zero length strings corresponding to user and
99      group specifiers or to NULL.  */
100   u = (*spec == '\0' ? NULL : spec);
101 
102   g = (separator == NULL || *(separator + 1) == '\0'
103        ? NULL
104        : separator + 1);
105 
106   if (u == NULL && g == NULL)
107     return "can not omit both user and group";
108 
109   if (u != NULL)
110     {
111       if (*u == '+')
112 	{
113 	  pwd = NULL;
114 	  ++u;
115 	}
116       else
117 	pwd = getpwnam (u);
118 
119       if (pwd == NULL)
120 	{
121 	  if (!isnumber_p (u))
122 	    error_msg = _("invalid user");
123 	  else
124 	    {
125 	      int use_login_group;
126 	      use_login_group = (separator != NULL && g == NULL);
127 	      if (use_login_group)
128 		error_msg = _("cannot get the login group of a numeric UID");
129 	      else
130 		*uid = atoi (u);
131 	    }
132 	}
133       else
134 	{
135 	  *uid = pwd->pw_uid;
136 	  if (g == NULL && separator != NULL)
137 	    {
138 	      /* A separator was given, but a group was not specified,
139 	         so get the login group.  */
140 	      *gid = pwd->pw_gid;
141 	      grp = getgrgid (pwd->pw_gid);
142 	      if (grp == NULL)
143 		{
144 		  /* This is enough room to hold the unsigned decimal
145 		     representation of any 32-bit quantity and the trailing
146 		     zero byte.  */
147 		  char uint_buf[21];
148 		  sprintf (uint_buf, "%u", (unsigned) (pwd->pw_gid));
149 		  V_STRDUP (groupname, uint_buf);
150 		}
151 	      else
152 		{
153 		  V_STRDUP (groupname, grp->gr_name);
154 		}
155 	      endgrent ();
156 	    }
157 	}
158       endpwent ();
159     }
160 
161   if (g != NULL && error_msg == NULL)
162     {
163       /* Explicit group.  */
164       if (*g == '+')
165 	{
166 	  grp = NULL;
167 	  ++g;
168 	}
169       else
170 	grp = getgrnam (g);
171 
172       if (grp == NULL)
173 	{
174 	  if (!isnumber_p (g))
175 	    error_msg = _("invalid group");
176 	  else
177 	    *gid = atoi (g);
178 	}
179       else
180 	*gid = grp->gr_gid;
181       endgrent ();		/* Save a file descriptor.  */
182 
183       if (error_msg == NULL)
184 	V_STRDUP (groupname, g);
185     }
186 
187   if (error_msg == NULL)
188     {
189       if (u != NULL)
190 	{
191 	  *username_arg = strdup (u);
192 	  if (*username_arg == NULL)
193 	    error_msg = tired;
194 	}
195 
196       if (groupname != NULL && error_msg == NULL)
197 	{
198 	  *groupname_arg = strdup (groupname);
199 	  if (*groupname_arg == NULL)
200 	    {
201 	      if (*username_arg != NULL)
202 		{
203 		  free (*username_arg);
204 		  *username_arg = NULL;
205 		}
206 	      error_msg = tired;
207 	    }
208 	}
209     }
210 
211   return error_msg;
212 }
213 
214 #ifdef TEST
215 
216 #define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s))
217 
218 int
main(int argc,char ** argv)219 main (int argc, char **argv)
220 {
221   int i;
222 
223   for (i = 1; i < argc; i++)
224     {
225       const char *e;
226       char *username, *groupname;
227       uid_t uid;
228       gid_t gid;
229       char *tmp;
230 
231       tmp = strdup (argv[i]);
232       e = parse_user_spec (tmp, &uid, &gid, &username, &groupname);
233       free (tmp);
234       printf ("%s: %u %u %s %s %s\n",
235 	      argv[i],
236 	      (unsigned int) uid,
237 	      (unsigned int) gid,
238 	      NULL_CHECK (username),
239 	      NULL_CHECK (groupname),
240 	      NULL_CHECK (e));
241     }
242 
243   exit (0);
244 }
245 
246 #endif
247