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