pw_group.c revision 286199
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 286199 2015-08-02 12:56:25Z 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 if (GETGRNAM(name) != NULL) 550 errx(EX_DATAERR, "group name `%s' already exists", name); 551 cnf = get_userconfig(cfg); 552 rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun, 553 pretty, precrypted); 554 if (nis && rc == EXIT_SUCCESS && nis_update() == 0) 555 pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated"); 556 557 return (rc); 558} 559 560int 561pw_group_mod(int argc, char **argv, char *arg1) 562{ 563 struct userconf *cnf; 564 struct group *grp = NULL; 565 const char *cfg = NULL; 566 char *oldmembers = NULL; 567 char *members = NULL; 568 char *newmembers = NULL; 569 char *newname = NULL; 570 char *name = NULL; 571 intmax_t id = -1; 572 int ch, rc, fd = -1; 573 bool quiet, pretty, dryrun, nis, precrypted; 574 575 quiet = pretty = dryrun = nis = precrypted = false; 576 577 if (arg1 != NULL) { 578 if (strspn(arg1, "0123456789") == strlen(arg1)) 579 id = pw_checkid(arg1, GID_MAX); 580 else 581 name = arg1; 582 } 583 584 while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) { 585 switch (ch) { 586 case 'C': 587 cfg = optarg; 588 break; 589 case 'q': 590 quiet = true; 591 break; 592 case 'n': 593 name = optarg; 594 break; 595 case 'g': 596 id = pw_checkid(optarg, GID_MAX); 597 break; 598 case 'd': 599 oldmembers = optarg; 600 break; 601 case 'l': 602 newname = optarg; 603 break; 604 case 'H': 605 if (fd != -1) 606 errx(EX_USAGE, "'-h' and '-H' are mutually " 607 "exclusive options"); 608 fd = pw_checkfd(optarg); 609 precrypted = true; 610 if (fd == '-') 611 errx(EX_USAGE, "-H expects a file descriptor"); 612 break; 613 case 'h': 614 if (fd != -1) 615 errx(EX_USAGE, "'-h' and '-H' are mutually " 616 "exclusive options"); 617 fd = pw_checkfd(optarg); 618 break; 619 case 'M': 620 members = optarg; 621 break; 622 case 'm': 623 newmembers = optarg; 624 break; 625 case 'N': 626 dryrun = true; 627 break; 628 case 'P': 629 pretty = true; 630 break; 631 case 'Y': 632 nis = true; 633 break; 634 } 635 } 636 if (quiet) 637 freopen(_PATH_DEVNULL, "w", stderr); 638 cnf = get_userconfig(cfg); 639 grp = getgroup(name, id, true); 640 if (name == NULL) 641 name = grp->gr_name; 642 if (id > 0) 643 grp->gr_gid = id; 644 645 if (newname != NULL) 646 grp->gr_name = pw_checkname(newname, 0); 647 648 grp_set_passwd(grp, true, fd, precrypted); 649 /* 650 * Keep the same logic as old code for now: 651 * if -M is passed, -d and -m are ignored 652 * then id -d, -m is ignored 653 * last is -m 654 */ 655 656 if (members) { 657 grp->gr_mem = NULL; 658 grp_add_members(&grp, members); 659 } else if (oldmembers) { 660 delete_members(grp, oldmembers); 661 } else if (newmembers) { 662 grp_add_members(&grp, newmembers); 663 } 664 665 if ((rc = chggrent(name, grp)) != 0) { 666 if (rc == -1) 667 errx(EX_IOERR, "group '%s' not available (NIS?)", 668 grp->gr_name); 669 else 670 err(EX_IOERR, "group update"); 671 } 672 673 if (newname) 674 name = newname; 675 676 /* grp may have been invalidated */ 677 if ((grp = GETGRNAM(name)) == NULL) 678 errx(EX_SOFTWARE, "group disappeared during update"); 679 680 pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name, 681 (uintmax_t)grp->gr_gid); 682 683 if (nis && nis_update() == 0) 684 pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated"); 685 686 return (EXIT_SUCCESS); 687} 688