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