pw_group.c revision 286196
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 286196 2015-08-02 12:47:50Z bapt $"; 30#endif /* not lint */ 31 32#include <ctype.h> 33#include <err.h> 34#include <grp.h> 35#include <inttypes.h> 36#include <libutil.h> 37#include <paths.h> 38#include <stdbool.h> 39#include <termios.h> 40#include <unistd.h> 41 42#include "pw.h" 43#include "bitmap.h" 44 45static struct passwd *lookup_pwent(const char *user); 46static void delete_members(struct group *grp, char *list); 47static int print_group(struct group * grp, bool pretty); 48static gid_t gr_gidpolicy(struct userconf * cnf, intmax_t id); 49 50static void 51grp_set_passwd(struct group *grp, bool update, int fd, bool precrypted) 52{ 53 int b; 54 int istty; 55 struct termios t, n; 56 char *p, line[256]; 57 58 if (fd == -1) 59 return; 60 61 if (fd == '-') { 62 grp->gr_passwd = "*"; /* No access */ 63 return; 64 } 65 66 if ((istty = isatty(fd))) { 67 n = t; 68 /* Disable echo */ 69 n.c_lflag &= ~(ECHO); 70 tcsetattr(fd, TCSANOW, &n); 71 printf("%sassword for group %s:", update ? "New p" : "P", 72 grp->gr_name); 73 fflush(stdout); 74 } 75 b = read(fd, line, sizeof(line) - 1); 76 if (istty) { /* Restore state */ 77 tcsetattr(fd, TCSANOW, &t); 78 fputc('\n', stdout); 79 fflush(stdout); 80 } 81 if (b < 0) 82 err(EX_OSERR, "-h file descriptor"); 83 line[b] = '\0'; 84 if ((p = strpbrk(line, " \t\r\n")) != NULL) 85 *p = '\0'; 86 if (!*line) 87 errx(EX_DATAERR, "empty password read on file descriptor %d", 88 conf.fd); 89 if (precrypted) { 90 if (strchr(line, ':') != 0) 91 errx(EX_DATAERR, "wrong encrypted passwrd"); 92 grp->gr_passwd = line; 93 } else 94 grp->gr_passwd = pw_pwcrypt(line); 95} 96 97int 98pw_groupnext(struct userconf *cnf, bool quiet) 99{ 100 gid_t next = gr_gidpolicy(cnf, -1); 101 102 if (quiet) 103 return (next); 104 printf("%ju\n", (uintmax_t)next); 105 106 return (EXIT_SUCCESS); 107} 108 109static struct group * 110getgroup(char *name, intmax_t id, bool fatal) 111{ 112 struct group *grp; 113 114 if (id < 0 && name == NULL) 115 errx(EX_DATAERR, "groupname or id required"); 116 grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id); 117 if (grp == NULL) { 118 if (!fatal) 119 return (NULL); 120 if (name == NULL) 121 errx(EX_DATAERR, "unknown gid `%ju'", id); 122 errx(EX_DATAERR, "unknown group `%s'", name); 123 } 124 return (grp); 125} 126 127/* 128 * Lookup a passwd entry using a name or UID. 129 */ 130static struct passwd * 131lookup_pwent(const char *user) 132{ 133 struct passwd *pwd; 134 135 if ((pwd = GETPWNAM(user)) == NULL && 136 (!isdigit((unsigned char)*user) || 137 (pwd = getpwuid((uid_t) atoi(user))) == NULL)) 138 errx(EX_NOUSER, "user `%s' does not exist", user); 139 140 return (pwd); 141} 142 143 144/* 145 * Delete requested members from a group. 146 */ 147static void 148delete_members(struct group *grp, char *list) 149{ 150 char *p; 151 int k; 152 153 if (grp->gr_mem == NULL) 154 return; 155 156 for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { 157 for (k = 0; grp->gr_mem[k] != NULL; k++) { 158 if (strcmp(grp->gr_mem[k], p) == 0) 159 break; 160 } 161 if (grp->gr_mem[k] == NULL) /* No match */ 162 continue; 163 164 for (; grp->gr_mem[k] != NULL; k++) 165 grp->gr_mem[k] = grp->gr_mem[k+1]; 166 } 167} 168 169static gid_t 170gr_gidpolicy(struct userconf * cnf, intmax_t id) 171{ 172 struct group *grp; 173 struct bitmap bm; 174 gid_t gid = (gid_t) - 1; 175 176 /* 177 * Check the given gid, if any 178 */ 179 if (id > 0) { 180 gid = (gid_t) id; 181 182 if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate) 183 errx(EX_DATAERR, "gid `%ju' has already been allocated", (uintmax_t)grp->gr_gid); 184 return (gid); 185 } 186 187 /* 188 * We need to allocate the next available gid under one of 189 * two policies a) Grab the first unused gid b) Grab the 190 * highest possible unused gid 191 */ 192 if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ 193 cnf->min_gid = 1000; 194 cnf->max_gid = 32000; 195 } 196 bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); 197 198 /* 199 * Now, let's fill the bitmap from the password file 200 */ 201 SETGRENT(); 202 while ((grp = GETGRENT()) != NULL) 203 if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && 204 (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) 205 bm_setbit(&bm, grp->gr_gid - cnf->min_gid); 206 ENDGRENT(); 207 208 /* 209 * Then apply the policy, with fallback to reuse if necessary 210 */ 211 if (cnf->reuse_gids) 212 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 213 else { 214 gid = (gid_t) (bm_lastset(&bm) + 1); 215 if (!bm_isset(&bm, gid)) 216 gid += cnf->min_gid; 217 else 218 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 219 } 220 221 /* 222 * Another sanity check 223 */ 224 if (gid < cnf->min_gid || gid > cnf->max_gid) 225 errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used"); 226 bm_dealloc(&bm); 227 return (gid); 228} 229 230static int 231print_group(struct group * grp, bool pretty) 232{ 233 char *buf = NULL; 234 int i; 235 236 if (pretty) { 237 printf("Group Name: %-15s #%lu\n" 238 " Members: ", 239 grp->gr_name, (long) grp->gr_gid); 240 if (grp->gr_mem != NULL) { 241 for (i = 0; grp->gr_mem[i]; i++) 242 printf("%s%s", i ? "," : "", grp->gr_mem[i]); 243 } 244 fputs("\n\n", stdout); 245 return (EXIT_SUCCESS); 246 } 247 248 buf = gr_make(grp); 249 printf("%s\n", buf); 250 free(buf); 251 return (EXIT_SUCCESS); 252} 253 254int 255pw_group_next(int argc, char **argv, char *arg1 __unused) 256{ 257 struct userconf *cnf; 258 const char *cfg = NULL; 259 int ch; 260 bool quiet; 261 262 while ((ch = getopt(argc, argv, "Cq")) != -1) { 263 switch (ch) { 264 case 'C': 265 cfg = optarg; 266 break; 267 case 'q': 268 quiet = true; 269 break; 270 } 271 } 272 273 if (quiet) 274 freopen(_PATH_DEVNULL, "w", stderr); 275 cnf = get_userconfig(cfg); 276 return (pw_groupnext(cnf, quiet)); 277} 278 279int 280pw_group_show(int argc, char **argv, char *arg1) 281{ 282 struct group *grp = NULL; 283 char *name; 284 intmax_t id = -1; 285 int ch; 286 bool all, force, quiet, pretty; 287 288 all = force = quiet = pretty = false; 289 290 struct group fakegroup = { 291 "nogroup", 292 "*", 293 -1, 294 NULL 295 }; 296 297 if (arg1 != NULL) { 298 if (strspn(arg1, "0123456789") == strlen(arg1)) 299 id = pw_checkid(arg1, GID_MAX); 300 else 301 name = arg1; 302 } 303 304 while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) { 305 switch (ch) { 306 case 'C': 307 /* ignore compatibility */ 308 break; 309 case 'q': 310 quiet = true; 311 break; 312 case 'n': 313 name = optarg; 314 break; 315 case 'g': 316 id = pw_checkid(optarg, GID_MAX); 317 break; 318 case 'F': 319 force = true; 320 break; 321 case 'P': 322 pretty = true; 323 break; 324 case 'a': 325 all = true; 326 break; 327 } 328 } 329 330 if (quiet) 331 freopen(_PATH_DEVNULL, "w", stderr); 332 333 if (all) { 334 SETGRENT(); 335 while ((grp = GETGRENT()) != NULL) 336 print_group(grp, pretty); 337 ENDGRENT(); 338 return (EXIT_SUCCESS); 339 } 340 341 grp = getgroup(name, id, !force); 342 if (grp == NULL) 343 grp = &fakegroup; 344 345 return (print_group(grp, pretty)); 346} 347 348int 349pw_group_del(int argc, char **argv, char *arg1) 350{ 351 struct userconf *cnf = NULL; 352 struct group *grp = NULL; 353 char *name; 354 const char *cfg = NULL; 355 intmax_t id = -1; 356 int ch, rc; 357 bool quiet = false; 358 bool nis = false; 359 360 if (arg1 != NULL) { 361 if (strspn(arg1, "0123456789") == strlen(arg1)) 362 id = pw_checkid(arg1, GID_MAX); 363 else 364 name = arg1; 365 } 366 367 while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) { 368 switch (ch) { 369 case 'C': 370 cfg = optarg; 371 break; 372 case 'q': 373 quiet = true; 374 break; 375 case 'n': 376 name = optarg; 377 break; 378 case 'g': 379 id = pw_checkid(optarg, GID_MAX); 380 break; 381 case 'Y': 382 nis = true; 383 break; 384 } 385 } 386 387 if (quiet) 388 freopen(_PATH_DEVNULL, "w", stderr); 389 grp = getgroup(name, id, true); 390 cnf = get_userconfig(cfg); 391 rc = delgrent(grp); 392 if (rc == -1) 393 err(EX_IOERR, "group '%s' not available (NIS?)", name); 394 else if (rc != 0) 395 err(EX_IOERR, "group update"); 396 pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name, 397 (uintmax_t)id); 398 399 if (nis && nis_update() == 0) 400 pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated"); 401 402 return (EXIT_SUCCESS); 403} 404 405static bool 406grp_has_member(struct group *grp, const char *name) 407{ 408 int j; 409 410 for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++) 411 if (strcmp(grp->gr_mem[j], name) == 0) 412 return (true); 413 return (false); 414} 415 416static void 417grp_add_members(struct group **grp, char *members) 418{ 419 struct passwd *pwd; 420 char *p; 421 char tok[] = ", \t"; 422 423 if (members == NULL) 424 return; 425 for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) { 426 pwd = lookup_pwent(p); 427 if (grp_has_member(*grp, pwd->pw_name)) 428 continue; 429 *grp = gr_add(*grp, pwd->pw_name); 430 } 431} 432 433int 434groupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd, 435 bool dryrun, bool pretty, bool precrypted) 436{ 437 struct group *grp; 438 int rc; 439 440 struct group fakegroup = { 441 "nogroup", 442 "*", 443 -1, 444 NULL 445 }; 446 447 grp = &fakegroup; 448 grp->gr_name = pw_checkname(name, 0); 449 grp->gr_passwd = "*"; 450 grp->gr_gid = gr_gidpolicy(cnf, id); 451 grp->gr_mem = NULL; 452 453 /* 454 * This allows us to set a group password Group passwords is an 455 * antique idea, rarely used and insecure (no secure database) Should 456 * be discouraged, but it is apparently still supported by some 457 * software. 458 */ 459 grp_set_passwd(grp, false, fd, precrypted); 460 grp_add_members(&grp, members); 461 if (dryrun) 462 return (print_group(grp, pretty)); 463 464 if ((rc = addgrent(grp)) != 0) { 465 if (rc == -1) 466 errx(EX_IOERR, "group '%s' already exists", 467 grp->gr_name); 468 else 469 err(EX_IOERR, "group update"); 470 } 471 472 pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name, 473 (uintmax_t)grp->gr_gid); 474 475 return (EXIT_SUCCESS); 476} 477 478int 479pw_group_add(int argc, char **argv, char *arg1) 480{ 481 struct userconf *cnf = NULL; 482 char *name = NULL; 483 char *members = NULL; 484 const char *cfg = NULL; 485 intmax_t id = -1; 486 int ch, rc, fd = -1; 487 bool quiet, precrypted, dryrun, pretty, nis; 488 489 quiet = precrypted = dryrun = pretty = nis = false; 490 491 if (arg1 != NULL) { 492 if (strspn(arg1, "0123456789") == strlen(arg1)) 493 id = pw_checkid(arg1, GID_MAX); 494 else 495 name = arg1; 496 } 497 498 while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) { 499 switch (ch) { 500 case 'C': 501 cfg = optarg; 502 break; 503 case 'q': 504 quiet = true; 505 break; 506 case 'n': 507 name = optarg; 508 break; 509 case 'g': 510 id = pw_checkid(optarg, GID_MAX); 511 break; 512 case 'H': 513 if (fd != -1) 514 errx(EX_USAGE, "'-h' and '-H' are mutually " 515 "exclusive options"); 516 fd = pw_checkfd(optarg); 517 precrypted = true; 518 if (fd == '-') 519 errx(EX_USAGE, "-H expects a file descriptor"); 520 break; 521 case 'h': 522 if (fd != -1) 523 errx(EX_USAGE, "'-h' and '-H' are mutually " 524 "exclusive options"); 525 fd = pw_checkfd(optarg); 526 break; 527 case 'M': 528 members = optarg; 529 break; 530 case 'o': 531 conf.checkduplicate = false; 532 break; 533 case 'N': 534 dryrun = true; 535 break; 536 case 'P': 537 pretty = true; 538 break; 539 case 'Y': 540 nis = true; 541 break; 542 } 543 } 544 545 if (quiet) 546 freopen(_PATH_DEVNULL, "w", stderr); 547 if (name == NULL) 548 errx(EX_DATAERR, "group name required"); 549 cnf = get_userconfig(cfg); 550 rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun, 551 pretty, precrypted); 552 if (nis && rc == EXIT_SUCCESS && nis_update() == 0) 553 pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated"); 554 555 return (rc); 556} 557 558int 559pw_group_mod(int argc, char **argv, char *arg1) 560{ 561 struct userconf *cnf; 562 struct group *grp = NULL; 563 const char *cfg = NULL; 564 char *oldmembers = NULL; 565 char *members = NULL; 566 char *newmembers = NULL; 567 char *newname = NULL; 568 char *name = NULL; 569 intmax_t id = -1; 570 int ch, rc, fd = -1; 571 bool quiet, pretty, dryrun, nis, precrypted; 572 573 quiet = pretty = dryrun = nis = precrypted = false; 574 575 if (arg1 != NULL) { 576 if (strspn(arg1, "0123456789") == strlen(arg1)) 577 id = pw_checkid(arg1, GID_MAX); 578 else 579 name = arg1; 580 } 581 582 while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) { 583 switch (ch) { 584 case 'C': 585 cfg = optarg; 586 break; 587 case 'q': 588 quiet = true; 589 break; 590 case 'n': 591 name = optarg; 592 break; 593 case 'g': 594 id = pw_checkid(optarg, GID_MAX); 595 break; 596 case 'd': 597 oldmembers = optarg; 598 break; 599 case 'l': 600 newname = optarg; 601 break; 602 case 'H': 603 if (fd != -1) 604 errx(EX_USAGE, "'-h' and '-H' are mutually " 605 "exclusive options"); 606 fd = pw_checkfd(optarg); 607 precrypted = true; 608 if (fd == '-') 609 errx(EX_USAGE, "-H expects a file descriptor"); 610 break; 611 case 'h': 612 if (fd != -1) 613 errx(EX_USAGE, "'-h' and '-H' are mutually " 614 "exclusive options"); 615 fd = pw_checkfd(optarg); 616 break; 617 case 'M': 618 members = optarg; 619 break; 620 case 'm': 621 newmembers = optarg; 622 break; 623 case 'N': 624 dryrun = true; 625 break; 626 case 'P': 627 pretty = true; 628 break; 629 case 'Y': 630 nis = true; 631 break; 632 } 633 } 634 if (quiet) 635 freopen(_PATH_DEVNULL, "w", stderr); 636 cnf = get_userconfig(cfg); 637 grp = getgroup(name, id, true); 638 if (name == NULL) 639 name = grp->gr_name; 640 if (id > 0) 641 grp->gr_gid = id; 642 643 if (newname != NULL) 644 grp->gr_name = pw_checkname(newname, 0); 645 646 grp_set_passwd(grp, true, fd, precrypted); 647 /* 648 * Keep the same logic as old code for now: 649 * if -M is passed, -d and -m are ignored 650 * then id -d, -m is ignored 651 * last is -m 652 */ 653 654 if (members) { 655 grp->gr_mem = NULL; 656 grp_add_members(&grp, members); 657 } else if (oldmembers) { 658 delete_members(grp, oldmembers); 659 } else if (newmembers) { 660 grp_add_members(&grp, newmembers); 661 } 662 663 if ((rc = chggrent(name, grp)) != 0) { 664 if (rc == -1) 665 errx(EX_IOERR, "group '%s' not available (NIS?)", 666 grp->gr_name); 667 else 668 err(EX_IOERR, "group update"); 669 } 670 671 if (newname) 672 name = newname; 673 674 /* grp may have been invalidated */ 675 if ((grp = GETGRNAM(name)) == NULL) 676 errx(EX_SOFTWARE, "group disappeared during update"); 677 678 pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name, 679 (uintmax_t)grp->gr_gid); 680 681 if (nis && nis_update() == 0) 682 pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated"); 683 684 return (EXIT_SUCCESS); 685} 686