pw_group.c revision 277652
1/*- 2 * Copyright (C) 1996 3 * David L. Nugent. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#ifndef lint 28static const char rcsid[] = 29 "$FreeBSD: head/usr.sbin/pw/pw_group.c 277652 2015-01-24 19:13:03Z bapt $"; 30#endif /* not lint */ 31 32#include <ctype.h> 33#include <err.h> 34#include <termios.h> 35#include <stdbool.h> 36#include <unistd.h> 37#include <grp.h> 38#include <libutil.h> 39 40#include "pw.h" 41#include "bitmap.h" 42 43 44static struct passwd *lookup_pwent(const char *user); 45static void delete_members(char ***members, int *grmembers, int *i, 46 struct carg *arg, struct group *grp); 47static int print_group(struct group * grp, int pretty); 48static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args); 49 50int 51pw_group(struct userconf * cnf, int mode, struct cargs * args) 52{ 53 int rc; 54 struct carg *a_newname = getarg(args, 'l'); 55 struct carg *a_name = getarg(args, 'n'); 56 struct carg *a_gid = getarg(args, 'g'); 57 struct carg *arg; 58 struct group *grp = NULL; 59 int grmembers = 0; 60 char **members = NULL; 61 62 static struct group fakegroup = 63 { 64 "nogroup", 65 "*", 66 -1, 67 NULL 68 }; 69 70 if (a_gid != NULL) { 71 const char *teststr; 72 teststr = a_gid->val; 73 if (*teststr == '-') 74 teststr++; 75 if (strspn(teststr, "0123456789") != strlen(teststr)) 76 errx(EX_USAGE, "-g expects a number"); 77 } 78 79 if (mode == M_LOCK || mode == M_UNLOCK) 80 errx(EX_USAGE, "'lock' command is not available for groups"); 81 82 /* 83 * With M_NEXT, we only need to return the 84 * next gid to stdout 85 */ 86 if (mode == M_NEXT) { 87 gid_t next = gr_gidpolicy(cnf, args); 88 if (getarg(args, 'q')) 89 return next; 90 printf("%ld\n", (long)next); 91 return EXIT_SUCCESS; 92 } 93 94 if (mode == M_PRINT && getarg(args, 'a')) { 95 int pretty = getarg(args, 'P') != NULL; 96 97 SETGRENT(); 98 while ((grp = GETGRENT()) != NULL) 99 print_group(grp, pretty); 100 ENDGRENT(); 101 return EXIT_SUCCESS; 102 } 103 if (a_gid == NULL) { 104 if (a_name == NULL) 105 errx(EX_DATAERR, "group name or id required"); 106 107 if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) { 108 (a_gid = a_name)->ch = 'g'; 109 a_name = NULL; 110 } 111 } 112 grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val)); 113 114 if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) { 115 if (a_name == NULL && grp == NULL) /* Try harder */ 116 grp = GETGRGID(atoi(a_gid->val)); 117 118 if (grp == NULL) { 119 if (mode == M_PRINT && getarg(args, 'F')) { 120 char *fmems[1]; 121 fmems[0] = NULL; 122 fakegroup.gr_name = a_name ? a_name->val : "nogroup"; 123 fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1; 124 fakegroup.gr_mem = fmems; 125 return print_group(&fakegroup, getarg(args, 'P') != NULL); 126 } 127 errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val); 128 } 129 if (a_name == NULL) /* Needed later */ 130 a_name = addarg(args, 'n', grp->gr_name); 131 132 /* 133 * Handle deletions now 134 */ 135 if (mode == M_DELETE) { 136 gid_t gid = grp->gr_gid; 137 138 rc = delgrent(grp); 139 if (rc == -1) 140 err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name); 141 else if (rc != 0) { 142 warn("group update"); 143 return EX_IOERR; 144 } 145 pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid); 146 return EXIT_SUCCESS; 147 } else if (mode == M_PRINT) 148 return print_group(grp, getarg(args, 'P') != NULL); 149 150 if (a_gid) 151 grp->gr_gid = (gid_t) atoi(a_gid->val); 152 153 if (a_newname != NULL) 154 grp->gr_name = pw_checkname((u_char *)a_newname->val, 0); 155 } else { 156 if (a_name == NULL) /* Required */ 157 errx(EX_DATAERR, "group name required"); 158 else if (grp != NULL) /* Exists */ 159 errx(EX_DATAERR, "group name `%s' already exists", a_name->val); 160 161 extendarray(&members, &grmembers, 200); 162 members[0] = NULL; 163 grp = &fakegroup; 164 grp->gr_name = pw_checkname((u_char *)a_name->val, 0); 165 grp->gr_passwd = "*"; 166 grp->gr_gid = gr_gidpolicy(cnf, args); 167 grp->gr_mem = members; 168 } 169 170 /* 171 * This allows us to set a group password Group passwords is an 172 * antique idea, rarely used and insecure (no secure database) Should 173 * be discouraged, but it is apparently still supported by some 174 * software. 175 */ 176 177 if ((arg = getarg(args, 'h')) != NULL || 178 (arg = getarg(args, 'H')) != NULL) { 179 if (strcmp(arg->val, "-") == 0) 180 grp->gr_passwd = "*"; /* No access */ 181 else { 182 int fd = atoi(arg->val); 183 int precrypt = (arg->ch == 'H'); 184 int b; 185 int istty = isatty(fd); 186 struct termios t; 187 char *p, line[256]; 188 189 if (istty) { 190 if (tcgetattr(fd, &t) == -1) 191 istty = 0; 192 else { 193 struct termios n = t; 194 195 /* Disable echo */ 196 n.c_lflag &= ~(ECHO); 197 tcsetattr(fd, TCSANOW, &n); 198 printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name); 199 fflush(stdout); 200 } 201 } 202 b = read(fd, line, sizeof(line) - 1); 203 if (istty) { /* Restore state */ 204 tcsetattr(fd, TCSANOW, &t); 205 fputc('\n', stdout); 206 fflush(stdout); 207 } 208 if (b < 0) { 209 warn("-h file descriptor"); 210 return EX_OSERR; 211 } 212 line[b] = '\0'; 213 if ((p = strpbrk(line, " \t\r\n")) != NULL) 214 *p = '\0'; 215 if (!*line) 216 errx(EX_DATAERR, "empty password read on file descriptor %d", fd); 217 if (precrypt) { 218 if (strchr(line, ':') != NULL) 219 return EX_DATAERR; 220 grp->gr_passwd = line; 221 } else 222 grp->gr_passwd = pw_pwcrypt(line); 223 } 224 } 225 226 if (((arg = getarg(args, 'M')) != NULL || 227 (arg = getarg(args, 'd')) != NULL || 228 (arg = getarg(args, 'm')) != NULL) && arg->val) { 229 int i = 0; 230 char *p; 231 struct passwd *pwd; 232 233 /* Make sure this is not stay NULL with -M "" */ 234 extendarray(&members, &grmembers, 200); 235 if (arg->ch == 'd') 236 delete_members(&members, &grmembers, &i, arg, grp); 237 else if (arg->ch == 'm') { 238 int k = 0; 239 240 if (grp->gr_mem != NULL) { 241 while (grp->gr_mem[k] != NULL) { 242 if (extendarray(&members, &grmembers, i + 2) != -1) 243 members[i++] = grp->gr_mem[k]; 244 k++; 245 } 246 } 247 } 248 249 if (arg->ch != 'd') 250 for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { 251 int j; 252 253 /* 254 * Check for duplicates 255 */ 256 pwd = lookup_pwent(p); 257 for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++) 258 ; 259 if (j == i && extendarray(&members, &grmembers, i + 2) != -1) 260 members[i++] = newstr(pwd->pw_name); 261 } 262 while (i < grmembers) 263 members[i++] = NULL; 264 grp->gr_mem = members; 265 } 266 267 if (getarg(args, 'N') != NULL) 268 return print_group(grp, getarg(args, 'P') != NULL); 269 270 if (mode == M_ADD && (rc = addgrent(grp)) != 0) { 271 if (rc == -1) 272 warnx("group '%s' already exists", grp->gr_name); 273 else 274 warn("group update"); 275 return EX_IOERR; 276 } else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) { 277 if (rc == -1) 278 warnx("group '%s' not available (NIS?)", grp->gr_name); 279 else 280 warn("group update"); 281 return EX_IOERR; 282 } 283 284 arg = a_newname != NULL ? a_newname : a_name; 285 /* grp may have been invalidated */ 286 if ((grp = GETGRNAM(arg->val)) == NULL) 287 errx(EX_SOFTWARE, "group disappeared during update"); 288 289 pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid); 290 291 free(members); 292 293 return EXIT_SUCCESS; 294} 295 296 297/* 298 * Lookup a passwd entry using a name or UID. 299 */ 300static struct passwd * 301lookup_pwent(const char *user) 302{ 303 struct passwd *pwd; 304 305 if ((pwd = GETPWNAM(user)) == NULL && 306 (!isdigit((unsigned char)*user) || 307 (pwd = getpwuid((uid_t) atoi(user))) == NULL)) 308 errx(EX_NOUSER, "user `%s' does not exist", user); 309 310 return (pwd); 311} 312 313 314/* 315 * Delete requested members from a group. 316 */ 317static void 318delete_members(char ***members, int *grmembers, int *i, struct carg *arg, 319 struct group *grp) 320{ 321 bool matchFound; 322 char *user; 323 char *valueCopy; 324 char *valuePtr; 325 int k; 326 struct passwd *pwd; 327 328 if (grp->gr_mem == NULL) 329 return; 330 331 k = 0; 332 while (grp->gr_mem[k] != NULL) { 333 matchFound = false; 334 if ((valueCopy = strdup(arg->val)) == NULL) 335 errx(EX_UNAVAILABLE, "out of memory"); 336 valuePtr = valueCopy; 337 while ((user = strsep(&valuePtr, ", \t")) != NULL) { 338 pwd = lookup_pwent(user); 339 if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) { 340 matchFound = true; 341 break; 342 } 343 } 344 free(valueCopy); 345 346 if (!matchFound && 347 extendarray(members, grmembers, *i + 2) != -1) 348 (*members)[(*i)++] = grp->gr_mem[k]; 349 350 k++; 351 } 352 353 return; 354} 355 356 357static gid_t 358gr_gidpolicy(struct userconf * cnf, struct cargs * args) 359{ 360 struct group *grp; 361 gid_t gid = (gid_t) - 1; 362 struct carg *a_gid = getarg(args, 'g'); 363 364 /* 365 * Check the given gid, if any 366 */ 367 if (a_gid != NULL) { 368 gid = (gid_t) atol(a_gid->val); 369 370 if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL) 371 errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid); 372 } else { 373 struct bitmap bm; 374 375 /* 376 * We need to allocate the next available gid under one of 377 * two policies a) Grab the first unused gid b) Grab the 378 * highest possible unused gid 379 */ 380 if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ 381 cnf->min_gid = 1000; 382 cnf->max_gid = 32000; 383 } 384 bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); 385 386 /* 387 * Now, let's fill the bitmap from the password file 388 */ 389 SETGRENT(); 390 while ((grp = GETGRENT()) != NULL) 391 if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && 392 (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) 393 bm_setbit(&bm, grp->gr_gid - cnf->min_gid); 394 ENDGRENT(); 395 396 /* 397 * Then apply the policy, with fallback to reuse if necessary 398 */ 399 if (cnf->reuse_gids) 400 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 401 else { 402 gid = (gid_t) (bm_lastset(&bm) + 1); 403 if (!bm_isset(&bm, gid)) 404 gid += cnf->min_gid; 405 else 406 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 407 } 408 409 /* 410 * Another sanity check 411 */ 412 if (gid < cnf->min_gid || gid > cnf->max_gid) 413 errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used"); 414 bm_dealloc(&bm); 415 } 416 return gid; 417} 418 419 420static int 421print_group(struct group * grp, int pretty) 422{ 423 if (!pretty) { 424 char *buf = NULL; 425 426 buf = gr_make(grp); 427 printf("%s\n", buf); 428 free(buf); 429 } else { 430 int i; 431 432 printf("Group Name: %-15s #%lu\n" 433 " Members: ", 434 grp->gr_name, (long) grp->gr_gid); 435 if (grp->gr_mem != NULL) { 436 for (i = 0; grp->gr_mem[i]; i++) 437 printf("%s%s", i ? "," : "", grp->gr_mem[i]); 438 } 439 fputs("\n\n", stdout); 440 } 441 return EXIT_SUCCESS; 442} 443