/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * Copyright (c) 1980, 1986, 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <sys/acl.h>
#include <sys/fs/ufs_acl.h>
#include <sys/fs/ufs_fs.h>
#include <sys/vnode.h>
#include <string.h>
#include <sys/fs/ufs_inode.h>
#include "fsck.h"

/*
 * We can be run on multiple filesystems (processed serially), so
 * these need to be re-initialized each time we start the pass.
 */
static caddr_t aclbuf;		/* hold acl's for parsing */
static int64_t aclbufoff;	/* offset into aclbuf */
static int64_t maxaclsize;	/* how big aclbuf is */

static int aclblksort(const void *, const void *);
static int bufchk(char *, int64_t, fsck_ino_t);
static void clear_shadow_client(struct shadowclientinfo *,
	    struct shadowclients *, int);

void
pass3b(void)
{
	fsck_ino_t inumber;
	struct dinode *dp;
	struct inoinfo *aclp;
	struct inodesc curino;
	struct shadowclientinfo *sci;
	struct shadowclients *scc;
	int64_t acl_size_limit;
	int i;

	/*
	 * Sort the acl list into disk block order.
	 */
	qsort((char *)aclpsort, (int)aclplast, sizeof (*aclpsort), aclblksort);
	/*
	 * Scan all the acl inodes, finding the largest acl file.
	 *
	 * The largest legal size is (4 * MAX_ACL_ENTRIES + 8) entries.
	 * The four are the categories of specific users, specific
	 * groups, default specific users, and default specific groups.
	 * The eight are the entries for the owning user/group/other/class
	 * plus the equivalent defaults.
	 *
	 * We double this to allow for a truly worst-case but legal
	 * situation of every single acl having its own fsd_t wrapper.
	 * Doubling is a bit pessimistic (sizeof (acl_t) > sizeof (fsd_t)).
	 */
	acl_size_limit = sizeof (ufs_acl_t) * (4 * MAX_ACL_ENTRIES + 8);
	acl_size_limit *= 2;

	maxaclsize = 0;
	for (inumber = 0; inumber < aclplast; inumber++) {
		aclp = aclpsort[inumber];
		if ((int64_t)aclp->i_isize > acl_size_limit) {
			(void) printf(
			    "ACL I=%d is excessively large (%lld > %lld)",
			    inumber,
			    (longlong_t)aclp->i_isize,
			    (longlong_t)acl_size_limit);
			if (preen) {
				(void) printf(" (IGNORING)\n");
			} else if (reply("CLEAR") == 1) {
				freeino(inumber, TI_PARENT);
			} else {
				iscorrupt = 1;
				(void) printf("IGNORING SHADOW I=%d\n",
				    inumber);
			}
			continue;
		}
		if ((int64_t)aclp->i_isize > maxaclsize)
			maxaclsize = (int64_t)aclp->i_isize;
	}

	maxaclsize = ((maxaclsize / sblock.fs_bsize) + 1) * sblock.fs_bsize;
	if (maxaclsize == 0)
		goto noacls;

	if (aclbuf != NULL) {
		free((void *)aclbuf);
	}
	if ((aclbuf = malloc(maxaclsize)) == NULL) {
		errexit("cannot alloc %lld bytes for aclbuf\n",
			(longlong_t)maxaclsize);
	}
	/*
	 * Scan all the acl inodes, checking contents
	 */
	for (inumber = 0; inumber < aclplast; inumber++) {
		aclp = aclpsort[inumber];
		if ((int64_t)aclp->i_isize > acl_size_limit) {
			continue;
		}
		if ((statemap[aclp->i_number] & STMASK) != SSTATE) {
			continue;
		}
		dp = ginode(aclp->i_number);
		init_inodesc(&curino);
		curino.id_fix = FIX;
		curino.id_type = ACL;
		curino.id_func = pass3bcheck;
		curino.id_number = aclp->i_number;
		curino.id_filesize = aclp->i_isize;
		aclbufoff = 0;
		(void) memset(aclbuf, 0, (size_t)maxaclsize);
		if ((ckinode(dp, &curino, CKI_TRAVERSE) & KEEPON) == 0 ||
		    bufchk(aclbuf, (int64_t)aclp->i_isize, aclp->i_number)) {
			dp = ginode(aclp->i_number); /* defensive no-op */
			if (dp->di_nlink <= 0) {
				statemap[aclp->i_number] = FSTATE;
				continue;
			}
			(void) printf("ACL I=%d BAD/CORRUPT", aclp->i_number);
			if (preen || reply("CLEAR") == 1) {
				if (preen)
					(void) printf("\n");
				freeino(aclp->i_number, TI_PARENT);
			} else {
				iscorrupt = 1;
			}
		}
	}
	/*
	 * Now scan all shadow inodes, checking that any inodes that previously
	 * had an acl still have an acl.
	 */
noacls:
	for (sci = shadowclientinfo; sci; sci = sci->next) {
		if ((statemap[sci->shadow] & STMASK) != SSTATE) {
			for (scc = sci->clients; scc; scc = scc->next) {
				for (i = 0; i < scc->nclients; i++) {
					clear_shadow_client(sci, scc, i);
				}
			}
		}
	}
	free((void *)aclbuf);
	aclbuf = NULL;
}

static void
clear_shadow_client(struct shadowclientinfo *sci, struct shadowclients *scc,
	int client)
{
	int suppress_update = 0;
	caddr_t flow;
	struct inodesc ldesc;
	struct dinode *dp;

	(void) printf("I=%d HAS BAD/CLEARED ACL I=%d",
	    scc->client[client], sci->shadow);
	if (preen || reply("FIX") == 1) {
		if (preen)
			(void) printf("\n");

		/*
		 * If we clear the ACL, then the permissions should
		 * be as restrictive as possible until the user can
		 * set it to something reasonable.  If we keep the
		 * ACL, then the permissions are pretty much
		 * irrelevant.  So, just always clear the permission
		 * bits.
		 */
		dp = ginode(scc->client[client]);
		dp->di_mode &= IFMT;
		dp->di_shadow = 0;
		inodirty();

		/*
		 * Decrement in-memory link count - pass1 made sure
		 * the shadow inode # is a valid inode number.  But
		 * first, see if we're going to overflow our sixteen
		 * bits.
		 */
		LINK_RANGE(flow, lncntp[dp->di_shadow], 1);
		if (flow != NULL) {
			LINK_CLEAR(flow, scc->client[client], dp->di_mode,
			    &ldesc);
			if (statemap[scc->client[client]] == USTATE)
				suppress_update = 1;
		}

		/*
		 * We don't touch the shadow's on-disk link count,
		 * because we've already cleared its state in pass3b().
		 * Here we're just trying to keep lncntp[] in sync, so
		 * we can detect spurious links.
		 */
		if (!suppress_update)
			TRACK_LNCNTP(sci->shadow, lncntp[sci->shadow]++);
	} else {
		iscorrupt = 1;
	}
}

/*
 * Collect all the (data) blocks of an acl file into a buffer.
 * Later we'll scan the buffer and validate the acl data.
 */
int
pass3bcheck(struct inodesc *idesc)
{
	struct bufarea *bp;
	size_t size, bsize;

	if (aclbufoff == idesc->id_filesize) {
		return (STOP);
	}
	bsize = size = sblock.fs_fsize * idesc->id_numfrags;
	if ((size + aclbufoff) > idesc->id_filesize)
		size = idesc->id_filesize - aclbufoff;
	if (aclbufoff + size > maxaclsize)
		errexit("acl size %lld exceeds maximum calculated "
			"size of %lld bytes",
			(longlong_t)aclbufoff + size, (longlong_t)maxaclsize);
	bp = getdatablk(idesc->id_blkno, bsize);
	if (bp->b_errs != 0) {
		brelse(bp);
		return (STOP);
	}
	(void) memmove((void *)(aclbuf + aclbufoff), (void *)bp->b_un.b_buf,
		(size_t)size);
	aclbufoff += size;
	brelse(bp);
	return (KEEPON);
}

/*
 * Routine to sort disk blocks.
 */
static int
aclblksort(const void *pp1, const void *pp2)
{
	const struct inoinfo **aclpp1 = (const struct inoinfo **)pp1;
	const struct inoinfo **aclpp2 = (const struct inoinfo **)pp2;

	return ((*aclpp1)->i_blks[0] - (*aclpp2)->i_blks[0]);
}

/*
 * Scan a chunk of a shadow file.  Return zero if no ACLs were found,
 * or when all that were found were valid.
 */
static int
bufchk(char *buf, int64_t len, fsck_ino_t inum)
{
	ufs_fsd_t *fsdp;
	ufs_acl_t *ufsaclp = NULL;
	int numacls;
	int curacl;
	struct type_counts_s {
		int nuser_objs;
		int ngroup_objs;
		int nother_objs;
		int nclass_objs;
		int ndef_user_objs;
		int ndef_group_objs;
		int ndef_other_objs;
		int ndef_class_objs;
		int nusers;
		int ngroups;
		int ndef_users;
		int ndef_groups;
	} type_counts[3];	/* indexed by FSD_ACL and FSD_DFACL */
	struct type_counts_s *tcp, *tcp_all, *tcp_def, *tcp_norm;
	int numdefs;
	caddr_t bad;
	caddr_t end = buf + len;
	int64_t recsz = 0;
	int64_t min_recsz = FSD_RECSZ(fsdp, sizeof (*fsdp));
	struct shadowclientinfo *sci;
	struct shadowclients *scc;
	fsck_ino_t target;
	int numtargets = 0;

	/*
	 * check we have a non-zero length for this shadow inode
	 */
	if (len == 0) {
		pwarn("ACL I=%d HAS ZERO LENGTH\n", inum);
		return (1);
	}

	(void) memset(type_counts, 0, sizeof (type_counts));

	/* LINTED pointer cast alignment (aligned buffer always passed in) */
	for (fsdp = (ufs_fsd_t *)buf;
	    (caddr_t)fsdp < end;
	    /* LINTED as per the above */
	    fsdp = (ufs_fsd_t *)((caddr_t)fsdp + recsz)) {

		recsz = FSD_RECSZ(fsdp, fsdp->fsd_size);
		if ((recsz < min_recsz) ||
		    (((caddr_t)fsdp + recsz) > (buf + len))) {
			pwarn("Bad FSD entry size %lld in shadow inode %d",
			    recsz, inum);
			if (reply("CLEAR SHADOW INODE") == 1) {
				freeino(inum, TI_PARENT);
			} else {
				/*
				 * Bad size can cause the kernel to
				 * go traipsing off into never-never land.
				 */
				iscorrupt = 1;
			}
			return (0);
		}

		switch (fsdp->fsd_type) {
		case FSD_FREE:	/* ignore empty slots */
			break;
		case FSD_ACL:
		case FSD_DFACL:
			/*
			 * Subtract out the two ints in the fsd_type,
			 * leaving us just the size of fsd_data[].
			 */
			numacls = (fsdp->fsd_size - 2 * sizeof (int)) /
							sizeof (ufs_acl_t);
			tcp = &type_counts[fsdp->fsd_type];
			curacl = 0;
			/* LINTED pointer cast alignment */
			for (ufsaclp = (ufs_acl_t *)fsdp->fsd_data;
						numacls; ufsaclp++, curacl++) {
				switch (ufsaclp->acl_tag) {
				case USER_OBJ:		/* Owner */
					tcp->nuser_objs++;
					break;
				case GROUP_OBJ:		/* Group */
					tcp->ngroup_objs++;
					break;
				case OTHER_OBJ:		/* Other */
					tcp->nother_objs++;
					break;
				case CLASS_OBJ:		/* Mask */
					tcp->nclass_objs++;
					break;
				case DEF_USER_OBJ:	/* Default Owner */
					tcp->ndef_user_objs++;
					break;
				case DEF_GROUP_OBJ:	/* Default Group */
					tcp->ndef_group_objs++;
					break;
				case DEF_OTHER_OBJ:	/* Default Other */
					tcp->ndef_other_objs++;
					break;
				case DEF_CLASS_OBJ:	/* Default Mask */
					tcp->ndef_class_objs++;
					break;
				case USER:		/* Users */
					tcp->nusers++;
					break;
				case GROUP:		/* Groups */
					tcp->ngroups++;
					break;
				case DEF_USER:		/* Default Users */
					tcp->ndef_users++;
					break;
				case DEF_GROUP:		/* Default Groups */
					tcp->ndef_groups++;
					break;
				default:
					return (1);
				}

				if ((ufsaclp->acl_perm & ~07) != 0) {
					/*
					 * Caller will report inode, etc
					 */
					pwarn("Bad permission 0%o in ACL\n",
					    ufsaclp->acl_perm);
					return (1);
				}

				numacls--;
			}
			break;
		default:
			if (fsdp->fsd_type >= FSD_RESERVED3 &&
			    fsdp->fsd_type <= FSD_RESERVED7)
				bad = "Unexpected";
			else
				bad = "Unknown";
			pwarn("%s FSD type %d in shadow inode %d",
			    bad, fsdp->fsd_type, inum);
			/*
			 * This is relatively harmless, since the
			 * kernel will ignore any entries it doesn't
			 * recognize.  Don't bother with iscorrupt.
			 */
			if (preen) {
				(void) printf(" (IGNORED)\n");
			} else if (reply("IGNORE") == 0) {
				if (reply("CLEAR SHADOW INODE") == 1) {
					freeino(inum, TI_PARENT);
				}
				return (0);
			}
			break;
		}
	}
	if ((caddr_t)fsdp != (buf + len)) {
		return (1);
	}

	/* If we didn't find any acls, ignore the unknown attribute */
	if (ufsaclp == NULL)
		return (0);

	/*
	 * Should only have default ACLs in FSD_DFACL records.
	 * However, the kernel can handle it, so just report that
	 * something odd might be going on.
	 */
	tcp = &type_counts[FSD_DFACL];
	if (verbose &&
	    (tcp->nuser_objs != 0 ||
	    tcp->ngroup_objs != 0 ||
	    tcp->nother_objs != 0 ||
	    tcp->nclass_objs != 0 ||
	    tcp->nusers != 0 ||
	    tcp->ngroups != 0)) {
		(void) printf("NOTE: ACL I=%d has miscategorized ACLs.  ",
		    inum);
		(void) printf("This is harmless, but not normal.\n");
	}

	/*
	 * Similarly for default ACLs in FSD_ACL records.
	 */
	tcp = &type_counts[FSD_ACL];
	if (verbose &&
	    (tcp->ndef_user_objs != 0 ||
	    tcp->ndef_group_objs != 0 ||
	    tcp->ndef_other_objs != 0 ||
	    tcp->ndef_class_objs != 0 ||
	    tcp->ndef_users != 0 ||
	    tcp->ndef_groups != 0)) {
		(void) printf("NOTE: ACL I=%d has miscategorized ACLs.",
		    inum);
		(void) printf("  This is harmless, but not normal.\n");
	}

	/*
	 * Get consolidated totals, now that we're done with checking
	 * the segregation above.  Assumes that neither FSD_ACL nor
	 * FSD_DFACL are zero.
	 */
	tcp_all = &type_counts[0];
	tcp_norm = &type_counts[FSD_ACL];
	tcp_def = &type_counts[FSD_DFACL];

	tcp_all->nuser_objs = tcp_def->nuser_objs + tcp_norm->nuser_objs;
	tcp_all->ngroup_objs = tcp_def->ngroup_objs + tcp_norm->ngroup_objs;
	tcp_all->nother_objs = tcp_def->nother_objs + tcp_norm->nother_objs;
	tcp_all->nclass_objs = tcp_def->nclass_objs + tcp_norm->nclass_objs;
	tcp_all->ndef_user_objs =
		tcp_def->ndef_user_objs + tcp_norm->ndef_user_objs;
	tcp_all->ndef_group_objs =
		tcp_def->ndef_group_objs + tcp_norm->ndef_group_objs;
	tcp_all->ndef_other_objs =
		tcp_def->ndef_other_objs + tcp_norm->ndef_other_objs;
	tcp_all->ndef_class_objs =
		tcp_def->ndef_class_objs + tcp_norm->ndef_class_objs;
	tcp_all->nusers = tcp_def->nusers + tcp_norm->nusers;
	tcp_all->ngroups = tcp_def->ngroups + tcp_norm->ngroups;
	tcp_all->ndef_users = tcp_def->ndef_users + tcp_norm->ndef_users;
	tcp_all->ndef_groups = tcp_def->ndef_groups + tcp_norm->ndef_groups;

	/*
	 * Check relationships among acls
	 */
	if (tcp_all->nuser_objs != 1 ||
	    tcp_all->ngroup_objs != 1 ||
	    tcp_all->nother_objs != 1 ||
	    tcp_all->nclass_objs > 1) {
		return (1);
	}

	if (tcp_all->ngroups && !tcp_all->nclass_objs) {
		return (1);
	}

	if (tcp_all->ndef_user_objs > 1 ||
	    tcp_all->ndef_group_objs > 1 ||
	    tcp_all->ndef_other_objs > 1 ||
	    tcp_all->ndef_class_objs > 1) {
		return (1);
	}

	/*
	 * Check relationships among default acls
	 */
	numdefs = tcp_all->ndef_other_objs + tcp_all->ndef_user_objs +
		tcp_all->ndef_group_objs;

	if (numdefs != 0 && numdefs != 3) {
		return (1);
	}

	/*
	 * If there are default acls, then the shadow inode's clients
	 * must be a directory or an xattr directory.
	 */
	if (numdefs != 0) {
		/* This is an ACL so find it's clients */
		for (sci = shadowclientinfo; sci != NULL; sci = sci->next)
			if (sci->shadow == inum)
			    break;
		if ((sci ==  NULL) || (sci->clients == NULL))
			return (1);

		/* Got shadow info, now look at clients */
		for (scc = sci->clients; scc != NULL; scc = scc->next) {
			for (numtargets = 0; numtargets < scc->nclients;
			    numtargets++) {
				target = scc->client[numtargets];
				if (!INO_IS_DVALID(target))
					return (1);
			}
		}
	}

	if (tcp_all->ndef_groups && !tcp_all->ndef_class_objs) {
		return (1);
	}

	if ((tcp_all->ndef_users || tcp_all->ndef_groups) &&
	    ((numdefs != 3) && !tcp_all->ndef_class_objs)) {
		return (1);
	}

	return (0);
}