/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <errno.h>
#include <locale.h>
#include <limits.h>

#define	BADLINE "Too many/few fields"
#define	TOOLONG "Line too long"
#define	NONAME	"No group name"
#define	BADNAME "Bad character(s) in group name"
#define	BADGID  "Invalid GID"
#define	NULLNAME "Null login name"
#define	NOTFOUND "Logname not found in password file"
#define	DUPNAME "Duplicate logname entry"
#define	DUPNAME2 "Duplicate logname entry (gid first occurs in passwd entry)"
#define	NOMEM	"Out of memory"
#define	NGROUPS	"Maximum groups exceeded for logname "
#define	BLANKLINE "Blank line detected. Please remove line"
#define	LONGNAME  "Group name too long"

int eflag, badchar, baddigit, badlognam, colons, len;
static int longnam = 0;
int code;

#define	MYBUFSIZE (LINE_MAX)	/* max line length including newline and null */
#define	NUM_COLONS	3

char *buf;
char *nptr;
char *cptr;
FILE *fptr;
gid_t gid;
void error(char *msg);

struct group {
	struct group *nxt;
	int cnt;
	gid_t grp;
};

struct node {
	struct node *next;
	int ngroups;
	struct group *groups;
	char user[1];
};

void *
emalloc(size_t size)
{
	void *vp;
	vp = malloc(size);
	if (vp == NULL) {
		fprintf(stderr, "%s\n", gettext(NOMEM));
		exit(1);
	}
	return (vp);
}

int
main(int argc, char *argv[])
{
	struct passwd *pwp;
	struct node *root = NULL;
	struct node *t;
	struct group *gp;
	int ngroups_max;
	int ngroups = 0;
	int listlen;
	int i;
	int lineno = 0;
	char *buf_off, *tmpbuf;
	int delim[NUM_COLONS + 1], buf_len, bufsize;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	code = 0;
	ngroups_max = sysconf(_SC_NGROUPS_MAX);

	if (argc == 1)
		argv[1] = "/etc/group";
	else if (argc != 2) {
		fprintf(stderr, gettext("usage: %s filename\n"), *argv);
		exit(1);
	}

	if ((fptr = fopen(argv[1], "r")) == NULL) {
		fprintf(stderr, gettext("cannot open file %s: %s\n"), argv[1],
			strerror(errno));
		exit(1);
	}

#ifdef ORIG_SVR4
	while ((pwp = getpwent()) != NULL) {
		t = (struct node *)emalloc(sizeof (*t) + strlen(pwp->pw_name));
		t->next = root;
		root = t;
		strcpy(t->user, pwp->pw_name);
		t->ngroups = 1;
		if (!ngroups_max)
			t->groups = NULL;
		else {
			t->groups = (struct group *)
				emalloc(sizeof (struct group));
			t->groups->grp = pwp->pw_gid;
			t->groups->cnt = 1;
			t->groups->nxt = NULL;
		}
	}
#endif

	bufsize = MYBUFSIZE;
	if ((buf = malloc(bufsize)) == NULL) {
		(void) fprintf(stderr, gettext(NOMEM));
		exit(1);
	}
	while (!feof(fptr) && !ferror(fptr)) {
		buf_len = 0;
		buf_off = buf;
		while (fgets(buf_off, (bufsize - buf_len), fptr) != NULL) {
			buf_len += strlen(buf_off);
			if (buf[buf_len - 1] == '\n' || feof(fptr))
				break;
			tmpbuf = realloc(buf, (bufsize + MYBUFSIZE));
			if (tmpbuf == NULL) {
				(void) fprintf(stderr, gettext(NOMEM));
				exit(1);
			}
			bufsize += MYBUFSIZE;
			buf = tmpbuf;
			buf_off = buf + buf_len;
		}
		if (buf_len == 0)
			continue;

		/* Report error to be consistent with libc */
		if ((buf_len + 1) > LINE_MAX)
			error(TOOLONG);

		lineno++;
		if (buf[0] == '\n')    /* blank lines are ignored */
		{
			code = 1;		/* exit with error code = 1 */
			eflag = 0;	/* force print of "blank" line */
			fprintf(stderr, "\n%s %d\n", gettext(BLANKLINE),
				lineno);
			continue;
		}

		if (buf[buf_len - 1] == '\n') {
			if ((tmpbuf = strdup(buf)) == NULL) {
				(void) fprintf(stderr, gettext(NOMEM));
				exit(1);
			}
			tmpbuf[buf_len - 1] = ',';
		} else {
			if ((tmpbuf = malloc(buf_len + 2)) == NULL) {
				(void) fprintf(stderr, gettext(NOMEM));
				exit(1);
			}
			(void) strcpy(tmpbuf, buf);
			tmpbuf[buf_len++] = ',';
			tmpbuf[buf_len] = '\0';
		}

		colons = 0;
		eflag = 0;
		badchar = 0;
		baddigit = 0;
		badlognam = 0;
		gid = (gid_t)0;

		ngroups++;	/* Increment number of groups found */
		/* Check that entry is not a nameservice redirection */

		if (buf[0] == '+' || buf[0] == '-')  {
			/*
			 * Should set flag here to allow special case checking
			 * in the rest of the code,
			 * but for now, we'll just ignore this entry.
			 */
			free(tmpbuf);
			continue;
		}

		/*	Check number of fields	*/

		for (i = 0; buf[i] != NULL; i++) {
			if (buf[i] == ':') {
				delim[colons] = i;
				if (++colons > NUM_COLONS)
					break;
			}
		}
		if (colons != NUM_COLONS) {
			error(BADLINE);
			free(tmpbuf);
			continue;
		}

		/* check to see that group name is at least 1 character	*/
		/* and that all characters are lowrcase or digits.	*/

		if (buf[0] == ':')
			error(NONAME);
		else {
			for (i = 0; buf[i] != ':'; i++) {
				if (i >= LOGNAME_MAX)
					longnam++;
				if (!(islower(buf[i]) || isdigit(buf[i])))
					badchar++;
			}
			if (longnam > 0)
				error(LONGNAME);
			if (badchar > 0)
				error(BADNAME);
		}

		/*	check that GID is numeric and <= 31 bits	*/

		len = (delim[2] - delim[1]) - 1;

		if (len > 10 || len < 1)
			error(BADGID);
		else {
			for (i = (delim[1]+1); i < delim[2]; i++) {
				if (! (isdigit(buf[i])))
					baddigit++;
				else if (baddigit == 0)
					gid = gid * 10 + (gid_t)(buf[i] - '0');
				/* converts ascii GID to decimal */
			}
			if (baddigit > 0)
				error(BADGID);
			else if (gid < (gid_t)0)
				error(BADGID);
		}

		/*  check that logname appears in the passwd file  */

		nptr = &tmpbuf[delim[2]];
		nptr++;

		listlen = strlen(nptr) - 1;

		while ((cptr = strchr(nptr, ',')) != NULL) {
			*cptr = NULL;
			if (*nptr == NULL) {
				if (listlen)
					error(NULLNAME);
				nptr++;
				continue;
			}

			for (t = root; t != NULL; t = t->next) {
				if (strcmp(t->user, nptr) == 0)
					break;
			}
			if (t == NULL) {
#ifndef ORIG_SVR4
				/*
				 * User entry not found, so check if in
				 *  password file
				 */
				struct passwd *pwp;

				if ((pwp = getpwnam(nptr)) == NULL) {
#endif
					badlognam++;
					error(NOTFOUND);
					goto getnext;
#ifndef ORIG_SVR4
				}

				/* Usrname found, so add entry to user-list */
				t = (struct node *)
					emalloc(sizeof (*t) + strlen(nptr));
				t->next = root;
				root = t;
				strcpy(t->user, nptr);
				t->ngroups = 1;
				if (!ngroups_max)
					t->groups = NULL;
				else {
					t->groups = (struct group *)
						emalloc(sizeof (struct group));
					t->groups->grp = pwp->pw_gid;
					t->groups->cnt = 1;
					t->groups->nxt = NULL;
				}
			}
#endif
			if (!ngroups_max)
				goto getnext;

			t->ngroups++;

			/*
			 * check for duplicate logname in group
			 */

			for (gp = t->groups; gp != NULL; gp = gp->nxt) {
				if (gid == gp->grp) {
					if (gp->cnt++ == 1) {
						badlognam++;
						if (gp->nxt == NULL)
							error(DUPNAME2);
						else
							error(DUPNAME);
					}
					goto getnext;
				}
			}

			gp = (struct group *)emalloc(sizeof (struct group));
			gp->grp = gid;
			gp->cnt = 1;
			gp->nxt = t->groups;
			t->groups = gp;
getnext:
			nptr = ++cptr;
		}
		free(tmpbuf);
	}

	if (ngroups == 0) {
		fprintf(stderr, gettext("Group file '%s' is empty\n"), argv[1]);
		code = 1;
	}

	if (ngroups_max) {
		for (t = root; t != NULL; t = t->next) {
			if (t->ngroups > ngroups_max) {
				fprintf(stderr, "\n\n%s%s (%d)\n",
				NGROUPS, t->user, t->ngroups);
				code = 1;
			}
		}
	}
	return (code);
}

/*	Error printing routine	*/

void
error(char *msg)
{
	code = 1;
	if (eflag == 0) {
		fprintf(stderr, "\n\n%s", buf);
		eflag = 1;
	}
	if (longnam != 0) {
		fprintf(stderr, "\t%s\n", gettext(msg));
		longnam = 0;
		return;
	}
	if (badchar != 0) {
		fprintf(stderr, "\t%d %s\n", badchar, gettext(msg));
		badchar = 0;
		return;
	} else if (baddigit != 0) {
		fprintf(stderr, "\t%s\n", gettext(msg));
		baddigit = 0;
		return;
	} else if (badlognam != 0) {
		fprintf(stderr, "\t%s - %s\n", nptr, gettext(msg));
		badlognam = 0;
		return;
	} else {
		fprintf(stderr, "\t%s\n", gettext(msg));
		return;
	}
}