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