]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/pw/pw_group.c
Fix regression: report if a group already exists when creating it
[FreeBSD/FreeBSD.git] / usr.sbin / pw / pw_group.c
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
28 static const char rcsid[] =
29   "$FreeBSD$";
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
45 static struct passwd *lookup_pwent(const char *user);
46 static void     delete_members(struct group *grp, char *list);
47 static int      print_group(struct group * grp, bool pretty);
48 static gid_t    gr_gidpolicy(struct userconf * cnf, intmax_t id);
49
50 static void
51 grp_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
97 int
98 pw_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
109 static struct group *
110 getgroup(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  */
130 static struct passwd *
131 lookup_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  */
147 static void
148 delete_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
169 static gid_t
170 gr_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
230 static int
231 print_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
254 int
255 pw_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
279 int
280 pw_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
348 int
349 pw_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
405 static bool
406 grp_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
416 static void
417 grp_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
433 int
434 groupadd(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
478 int
479 pw_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
560 int
561 pw_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 }