]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/pw/pw_group.c
Add two missing eventhandler.h headers
[FreeBSD/FreeBSD.git] / usr.sbin / 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 }