pw_group.c revision 20253
120253Sjoerg/*-
220253Sjoerg * Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
320253Sjoerg * All rights reserved.
420253Sjoerg *
520253Sjoerg * Redistribution and use in source and binary forms, with or without
620253Sjoerg * modification, are permitted provided that the following conditions
720253Sjoerg * are met:
820253Sjoerg * 1. Redistributions of source code must retain the above copyright
920253Sjoerg *    notice, this list of conditions and the following disclaimer as
1020253Sjoerg *    the first lines of this file unmodified.
1120253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright
1220253Sjoerg *    notice, this list of conditions and the following disclaimer in the
1320253Sjoerg *    documentation and/or other materials provided with the distribution.
1420253Sjoerg * 3. All advertising materials mentioning features or use of this software
1520253Sjoerg *    must display the following acknowledgement:
1620253Sjoerg *	This product includes software developed by David L. Nugent.
1720253Sjoerg * 4. The name of the author may not be used to endorse or promote products
1820253Sjoerg *    derived from this software without specific prior written permission.
1920253Sjoerg *
2020253Sjoerg * THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
2120253Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2220253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2320253Sjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
2420253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2520253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2620253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2720253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2820253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2920253Sjoerg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3020253Sjoerg * SUCH DAMAGE.
3120253Sjoerg *
3220253Sjoerg *	$Id$
3320253Sjoerg */
3420253Sjoerg
3520253Sjoerg#include <unistd.h>
3620253Sjoerg#include <ctype.h>
3720253Sjoerg#include <termios.h>
3820253Sjoerg
3920253Sjoerg#include "pw.h"
4020253Sjoerg#include "bitmap.h"
4120253Sjoerg
4220253Sjoerg
4320253Sjoergstatic int      print_group(struct group * grp, int pretty);
4420253Sjoergstatic gid_t    gr_gidpolicy(struct userconf * cnf, struct cargs * args);
4520253Sjoerg
4620253Sjoergint
4720253Sjoergpw_group(struct userconf * cnf, int mode, struct cargs * args)
4820253Sjoerg{
4920253Sjoerg	struct carg    *a_name = getarg(args, 'n');
5020253Sjoerg	struct carg    *a_gid = getarg(args, 'g');
5120253Sjoerg	struct carg    *arg;
5220253Sjoerg	struct group   *grp = NULL;
5320253Sjoerg	char           *members[_UC_MAXGROUPS];
5420253Sjoerg
5520253Sjoerg	static struct group fakegroup =
5620253Sjoerg	{
5720253Sjoerg		"nogroup",
5820253Sjoerg		"*",
5920253Sjoerg		-1,
6020253Sjoerg		NULL
6120253Sjoerg	};
6220253Sjoerg
6320253Sjoerg	if (mode == M_PRINT && getarg(args, 'a')) {
6420253Sjoerg		int             pretty = getarg(args, 'p') != NULL;
6520253Sjoerg
6620253Sjoerg		setgrent();
6720253Sjoerg		while ((grp = getgrent()) != NULL)
6820253Sjoerg			print_group(grp, pretty);
6920253Sjoerg		endgrent();
7020253Sjoerg		return X_ALLOK;
7120253Sjoerg	}
7220253Sjoerg	if (a_gid == NULL) {
7320253Sjoerg		if (a_name == NULL)
7420253Sjoerg			cmderr(X_CMDERR, "group name or id required\n");
7520253Sjoerg
7620253Sjoerg		if (mode != M_ADD && grp == NULL && isdigit(*a_name->val)) {
7720253Sjoerg			(a_gid = a_name)->ch = 'g';
7820253Sjoerg			a_name = NULL;
7920253Sjoerg		}
8020253Sjoerg	}
8120253Sjoerg	grp = (a_name != NULL) ? getgrnam(a_name->val) : getgrgid((gid_t) atoi(a_gid->val));
8220253Sjoerg
8320253Sjoerg	if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
8420253Sjoerg		if (a_name == NULL && grp == NULL)	/* Try harder */
8520253Sjoerg			grp = getgrgid(atoi(a_gid->val));
8620253Sjoerg
8720253Sjoerg		if (grp == NULL) {
8820253Sjoerg			if (mode == M_PRINT && getarg(args, 'F')) {
8920253Sjoerg				fakegroup.gr_name = a_name ? a_name->val : "nogroup";
9020253Sjoerg				fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
9120253Sjoerg				return print_group(&fakegroup, getarg(args, 'p') != NULL);
9220253Sjoerg			}
9320253Sjoerg			cmderr(X_CMDERR, "unknown group `%s'\n", a_name ? a_name->val : a_gid->val);
9420253Sjoerg		}
9520253Sjoerg		if (a_name == NULL)	/* Needed later */
9620253Sjoerg			a_name = addarg(args, 'n', grp->gr_name);
9720253Sjoerg
9820253Sjoerg		/*
9920253Sjoerg		 * Handle deletions now
10020253Sjoerg		 */
10120253Sjoerg		if (mode == M_DELETE) {
10220253Sjoerg			gid_t           gid = grp->gr_gid;
10320253Sjoerg
10420253Sjoerg			if (delgrent(grp) == -1)
10520253Sjoerg				cmderr(X_NOUPDATE, "Error updating group file: %s\n", strerror(errno));
10620253Sjoerg			pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
10720253Sjoerg			return X_ALLOK;
10820253Sjoerg		} else if (mode == M_PRINT)
10920253Sjoerg			return print_group(grp, getarg(args, 'p') != NULL);
11020253Sjoerg
11120253Sjoerg		if (a_gid)
11220253Sjoerg			grp->gr_gid = (gid_t) atoi(a_gid->val);
11320253Sjoerg
11420253Sjoerg		if ((arg = getarg(args, 'l')) != NULL)
11520253Sjoerg			grp->gr_name = arg->val;
11620253Sjoerg	} else {
11720253Sjoerg		if (a_name == NULL)	/* Required */
11820253Sjoerg			cmderr(X_CMDERR, "group name required\n");
11920253Sjoerg		else if (grp != NULL)	/* Exists */
12020253Sjoerg			cmderr(X_EXISTS, "group name `%s' already exists\n", a_name->val);
12120253Sjoerg
12220253Sjoerg		memset(members, 0, sizeof members);
12320253Sjoerg		grp = &fakegroup;
12420253Sjoerg		grp->gr_name = a_name->val;
12520253Sjoerg		grp->gr_passwd = "*";
12620253Sjoerg		grp->gr_gid = gr_gidpolicy(cnf, args);
12720253Sjoerg		grp->gr_mem = members;
12820253Sjoerg	}
12920253Sjoerg
13020253Sjoerg	/*
13120253Sjoerg	 * This allows us to set a group password Group passwords is an
13220253Sjoerg	 * antique idea, rarely used and insecure (no secure database) Should
13320253Sjoerg	 * be discouraged, but it is apparently still supported by some
13420253Sjoerg	 * software.
13520253Sjoerg	 */
13620253Sjoerg
13720253Sjoerg	if ((arg = getarg(args, 'h')) != NULL) {
13820253Sjoerg		if (strcmp(arg->val, "-") == 0)
13920253Sjoerg			grp->gr_passwd = "*";	/* No access */
14020253Sjoerg		else {
14120253Sjoerg			int             fd = atoi(arg->val);
14220253Sjoerg			int             b;
14320253Sjoerg			int             istty = isatty(fd);
14420253Sjoerg			struct termios  t;
14520253Sjoerg			char           *p, line[256];
14620253Sjoerg
14720253Sjoerg			if (istty) {
14820253Sjoerg				if (tcgetattr(fd, &t) == -1)
14920253Sjoerg					istty = 0;
15020253Sjoerg				else {
15120253Sjoerg					struct termios  n = t;
15220253Sjoerg
15320253Sjoerg					/* Disable echo */
15420253Sjoerg					n.c_lflag &= ~(ECHO);
15520253Sjoerg					tcsetattr(fd, TCSANOW, &n);
15620253Sjoerg					printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
15720253Sjoerg					fflush(stdout);
15820253Sjoerg				}
15920253Sjoerg			}
16020253Sjoerg			b = read(fd, line, sizeof(line) - 1);
16120253Sjoerg			if (istty) {	/* Restore state */
16220253Sjoerg				tcsetattr(fd, TCSANOW, &t);
16320253Sjoerg				fputc('\n', stdout);
16420253Sjoerg				fflush(stdout);
16520253Sjoerg			}
16620253Sjoerg			if (b < 0) {
16720253Sjoerg				perror("-h file descriptor");
16820253Sjoerg				return X_CMDERR;
16920253Sjoerg			}
17020253Sjoerg			line[b] = '\0';
17120253Sjoerg			if ((p = strpbrk(line, " \t\r\n")) != NULL)
17220253Sjoerg				*p = '\0';
17320253Sjoerg			if (!*line)
17420253Sjoerg				cmderr(X_CMDERR, "empty password read on file descriptor %d\n", fd);
17520253Sjoerg			grp->gr_passwd = pw_pwcrypt(line);
17620253Sjoerg		}
17720253Sjoerg	}
17820253Sjoerg	if ((mode == M_ADD && !addgrent(grp)) || (mode == M_UPDATE && !chggrent(a_name->val, grp))) {
17920253Sjoerg		perror("group update");
18020253Sjoerg		return X_NOUPDATE;
18120253Sjoerg	}
18220253Sjoerg	/* grp may have been invalidated */
18320253Sjoerg	if ((grp = getgrnam(a_name->val)) == NULL)
18420253Sjoerg		cmderr(X_NOTFOUND, "group disappeared during update\n");
18520253Sjoerg
18620253Sjoerg	pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
18720253Sjoerg
18820253Sjoerg	return X_ALLOK;
18920253Sjoerg}
19020253Sjoerg
19120253Sjoerg
19220253Sjoergstatic          gid_t
19320253Sjoerggr_gidpolicy(struct userconf * cnf, struct cargs * args)
19420253Sjoerg{
19520253Sjoerg	struct group   *grp;
19620253Sjoerg	gid_t           gid = (gid_t) - 1;
19720253Sjoerg	struct carg    *a_gid = getarg(args, 'g');
19820253Sjoerg
19920253Sjoerg	/*
20020253Sjoerg	 * Check the given gid, if any
20120253Sjoerg	 */
20220253Sjoerg	if (a_gid != NULL) {
20320253Sjoerg		gid = (gid_t) atol(a_gid->val);
20420253Sjoerg
20520253Sjoerg		if ((grp = getgrgid(gid)) != NULL && getarg(args, 'o') == NULL)
20620253Sjoerg			cmderr(X_EXISTS, "gid `%ld' has already been allocated\n", (long) grp->gr_gid);
20720253Sjoerg	} else {
20820253Sjoerg		struct bitmap   bm;
20920253Sjoerg
21020253Sjoerg		/*
21120253Sjoerg		 * We need to allocate the next available gid under one of
21220253Sjoerg		 * two policies a) Grab the first unused gid b) Grab the
21320253Sjoerg		 * highest possible unused gid
21420253Sjoerg		 */
21520253Sjoerg		if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
21620253Sjoerg			cnf->min_gid = 1000;
21720253Sjoerg			cnf->max_gid = 32000;
21820253Sjoerg		}
21920253Sjoerg		bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
22020253Sjoerg
22120253Sjoerg		/*
22220253Sjoerg		 * Now, let's fill the bitmap from the password file
22320253Sjoerg		 */
22420253Sjoerg		setgrent();
22520253Sjoerg		while ((grp = getgrent()) != NULL)
22620253Sjoerg			if (grp->gr_gid >= (int) cnf->min_gid && grp->gr_gid <= (int) cnf->max_gid)
22720253Sjoerg				bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
22820253Sjoerg		endgrent();
22920253Sjoerg
23020253Sjoerg		/*
23120253Sjoerg		 * Then apply the policy, with fallback to reuse if necessary
23220253Sjoerg		 */
23320253Sjoerg		if (cnf->reuse_gids)
23420253Sjoerg			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
23520253Sjoerg		else {
23620253Sjoerg			gid = (gid_t) (bm_lastset(&bm) + 1);
23720253Sjoerg			if (!bm_isset(&bm, gid))
23820253Sjoerg				gid += cnf->min_gid;
23920253Sjoerg			else
24020253Sjoerg				gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
24120253Sjoerg		}
24220253Sjoerg
24320253Sjoerg		/*
24420253Sjoerg		 * Another sanity check
24520253Sjoerg		 */
24620253Sjoerg		if (gid < cnf->min_gid || gid > cnf->max_gid)
24720253Sjoerg			cmderr(X_EXISTS, "unable to allocate a new gid - range fully used\n");
24820253Sjoerg		bm_dealloc(&bm);
24920253Sjoerg	}
25020253Sjoerg	return gid;
25120253Sjoerg}
25220253Sjoerg
25320253Sjoerg
25420253Sjoergstatic int
25520253Sjoergprint_group(struct group * grp, int pretty)
25620253Sjoerg{
25720253Sjoerg	if (!pretty) {
25820253Sjoerg		char            buf[_UC_MAXLINE];
25920253Sjoerg
26020253Sjoerg		fmtgrent(buf, grp);
26120253Sjoerg		fputs(buf, stdout);
26220253Sjoerg	} else {
26320253Sjoerg		int             i;
26420253Sjoerg
26520253Sjoerg		printf("Group Name : %-10s   #%lu\n"
26620253Sjoerg		       "   Members : ",
26720253Sjoerg		       grp->gr_name, (long) grp->gr_gid);
26820253Sjoerg		for (i = 0; i < _UC_MAXGROUPS && grp->gr_mem[i]; i++)
26920253Sjoerg			printf("%s%s", i ? "," : "", grp->gr_mem[i]);
27020253Sjoerg		fputs("\n\n", stdout);
27120253Sjoerg	}
27220253Sjoerg	return X_ALLOK;
27320253Sjoerg}
274