1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29
30#pragma ident	"%Z%%M%	%I%	%E% SMI"
31
32#include <sys/types.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <ctype.h>
37#include <sys/stat.h>
38#include <grp.h>
39#include <unistd.h>
40#include <userdefs.h>
41#include <errno.h>
42#include <limits.h>
43#include "users.h"
44#include "messages.h"
45
46#define	MYBUFSIZE (LINE_MAX)
47	/* Corresponds to MYBUFSIZE in grpck.c, BUFCONST in nss_dbdefs.c */
48
49int
50edit_group(char *login, char *new_login, gid_t gids[], int overwrite)
51{
52	char **memptr;
53	char t_name[] = "/etc/gtmp.XXXXXX";
54	int fd;
55	FILE *e_fptr, *t_fptr;
56	struct group *g_ptr;	/* group structure from fgetgrent */
57	int i;
58	int modified = 0;
59	struct stat sbuf;
60
61	int bufsize, g_length, sav_errno;
62	long g_curr = 0L;
63	char *g_string, *new_g_string, *gstr_off;
64
65	if ((e_fptr = fopen(GROUP, "r")) == NULL)
66		return (EX_UPDATE);
67
68	if (fstat(fileno(e_fptr), &sbuf) != 0) {
69		(void) fclose(e_fptr);
70		return (EX_UPDATE);
71	}
72
73	if ((fd = mkstemp(t_name)) == -1) {
74		(void) fclose(e_fptr);
75		return (EX_UPDATE);
76	}
77
78	if ((t_fptr = fdopen(fd, "w")) == NULL) {
79		(void) close(fd);
80		(void) unlink(t_name);
81		(void) fclose(e_fptr);
82		return (EX_UPDATE);
83	}
84
85	/*
86	 * Get ownership and permissions correct
87	 */
88
89	if (fchmod(fd, sbuf.st_mode) != 0 ||
90	    fchown(fd, sbuf.st_uid, sbuf.st_gid) != 0) {
91		(void) fclose(t_fptr);
92		(void) fclose(e_fptr);
93		(void) unlink(t_name);
94		return (EX_UPDATE);
95	}
96
97	g_curr = ftell(e_fptr);
98
99	/* Make TMP file look like we want GROUP file to look */
100
101	bufsize = MYBUFSIZE;
102	if ((g_string = malloc(bufsize)) == NULL) {
103		(void) fclose(t_fptr);
104		(void) fclose(e_fptr);
105		(void) unlink(t_name);
106		return (EX_UPDATE);
107	}
108	/*
109	 * bufsize contains the size of the currently allocated buffer
110	 * buffer size, which is initially MYBUFSIZE but when a line
111	 * greater than MYBUFSIZE is encountered then bufsize gets increased
112	 * by MYBUFSIZE.
113	 * g_string always points to the beginning of the buffer (even after
114	 * realloc()).
115	 * gstr_off = g_string + MYBUFSIZE * (n), where n >= 0.
116	 */
117	while (!feof(e_fptr) && !ferror(e_fptr)) {
118		g_length = 0;
119		gstr_off = g_string;
120		while (fgets(gstr_off, (bufsize - g_length), e_fptr) != NULL) {
121			g_length += strlen(gstr_off);
122			if (g_string[g_length - 1] == '\n' || feof(e_fptr))
123				break;
124			new_g_string = realloc(g_string, (bufsize + MYBUFSIZE));
125			if (new_g_string == NULL) {
126				free(g_string);
127				(void) fclose(t_fptr);
128				(void) fclose(e_fptr);
129				(void) unlink(t_name);
130				return (EX_UPDATE);
131			}
132			bufsize += MYBUFSIZE;
133			g_string = new_g_string;
134			gstr_off = g_string + g_length;
135		}
136		if (g_length == 0) {
137			continue;
138		}
139
140		/* While there is another group string */
141
142		(void) fseek(e_fptr, g_curr, SEEK_SET);
143		errno = 0;
144		g_ptr = fgetgrent(e_fptr);
145		sav_errno = errno;
146		g_curr = ftell(e_fptr);
147
148		if (g_ptr == NULL) {
149			/* tried to parse a group string over MYBUFSIZ char */
150			if (sav_errno == ERANGE)
151				errmsg(M_GROUP_ENTRY_OVF);
152			else
153				errmsg(M_READ_ERROR);
154
155			modified = 0; /* bad group file: cannot rebuild */
156			break;
157		}
158
159		/* first delete the login from the group, if it's there */
160		if (overwrite || !gids) {
161			if (g_ptr->gr_mem != NULL) {
162				for (memptr = g_ptr->gr_mem; *memptr;
163				    memptr++) {
164					if (strcmp(*memptr, login) == 0) {
165						/* Delete this one */
166						char **from = memptr + 1;
167
168						g_length -= (strlen(*memptr)+1);
169
170						do {
171							*(from - 1) = *from;
172						} while (*from++);
173
174						modified++;
175						break;
176					}
177				}
178			}
179		}
180
181		/* now check to see if group is one to add to */
182		if (gids) {
183			for (i = 0; gids[i] != -1; i++) {
184				if (g_ptr->gr_gid == gids[i]) {
185					/* Find end */
186					for (memptr = g_ptr->gr_mem; *memptr;
187						memptr++)
188						;
189					g_length += strlen(new_login ?
190							new_login : login)+1;
191
192					*memptr++ = new_login ?
193						new_login : login;
194					*memptr = NULL;
195
196					modified++;
197				}
198			}
199		}
200		putgrent(g_ptr, t_fptr);
201	}
202	free(g_string);
203
204	(void) fclose(e_fptr);
205
206	if (fclose(t_fptr) != 0) {
207		(void) unlink(t_name);
208		return (EX_UPDATE);
209	}
210
211	/* Now, update GROUP file, if it was modified */
212	if (modified) {
213		if (rename(t_name, GROUP) != 0) {
214			(void) unlink(t_name);
215			return (EX_UPDATE);
216		}
217		return (EX_SUCCESS);
218	} else {
219		(void) unlink(t_name);
220		return (EX_SUCCESS);
221	}
222}
223