/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2009 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * apply file permission expression expr to perm
 *
 * each expression term must match
 *
 *	[ugoa]*[-&+|^=]?[rwxst0-7]*
 *
 * terms may be combined using ,
 *
 * if non-null, e points to the first unrecognized char in expr
 */

#include <ast.h>
#include <ls.h>
#include <modex.h>

int
strperm(const char* aexpr, char** e, register int perm)
{
	register char*	expr = (char*)aexpr;
	register int	c;
	register int	typ;
	register int	who;
	int		num;
	int		op;
	int		mask;
	int		masked;

	if (perm == -1)
	{
		perm = 0;
		masked = 1;
		mask = ~0;
	}
	else
		masked = 0;
	for (;;)
	{
		op = num = who = typ = 0;
		for (;;)
		{
			switch (c = *expr++)
			{
			case 'u':
				who |= S_ISVTX|S_ISUID|S_IRWXU;
				continue;
			case 'g':
				who |= S_ISVTX|S_ISGID|S_IRWXG;
				continue;
			case 'o':
				who |= S_ISVTX|S_IRWXO;
				continue;
			case 'a':
				who = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
				continue;
			default:
				if (c >= '0' && c <= '7')
				{
					if (!who)
						who = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
					c = '=';
				}
				expr--;
				/*FALLTHROUGH*/
			case '=':
				if (who)
					perm &= ~who;
				else
					perm = 0;
				/*FALLTHROUGH*/
			case '+':
			case '|':
			case '-':
			case '&':
			case '^':
				op = c;
				for (;;)
				{
					switch (c = *expr++)
					{
					case 'r':
						typ |= S_IRUSR|S_IRGRP|S_IROTH;
						continue;
					case 'w':
						typ |= S_IWUSR|S_IWGRP|S_IWOTH;
						continue;
					case 'X':
						if (!S_ISDIR(perm) && !(perm & (S_IXUSR|S_IXGRP|S_IXOTH)))
							continue;
						/*FALLTHROUGH*/
					case 'x':
						typ |= S_IXUSR|S_IXGRP|S_IXOTH;
						continue;
					case 's':
						typ |= S_ISUID|S_ISGID;
						continue;
					case 't':
						typ |= S_ISVTX;
						continue;
					case 'l':
						if (perm & S_IXGRP)
						{
							if (e)
								*e = expr - 1;
							return perm & S_IPERM;
						}
						typ |= S_ISGID;
						continue;
					case '=':
					case '+':
					case '|':
					case '-':
					case '&':
					case '^':
					case ',':
					case 0:
						if (who)
							typ &= who;
						else
							switch (op)
							{
							case '=':
							case '+':
							case '|':
							case '-':
							case '&':
								if (!masked)
								{
									masked = 1;
									umask(mask = umask(0));
									mask = ~mask;
								}
								typ &= mask;
								break;
							}
						switch (op)
						{
						default:
							if (who)
								perm &= ~who;
							else
								perm = 0;
							/*FALLTHROUGH*/
						case '+':
						case '|':
							perm |= typ;
							typ = 0;
							break;
						case '-':
							perm &= ~typ;
							typ = 0;
							break;
						case '&':
							perm &= typ;
							typ = 0;
							break;
						case '^':
							if (typ &= perm)
							{
								/*
								 * propagate least restrictive to most restrictive
								 */

								if (typ & S_IXOTH)
									perm |= who & (S_IXUSR|S_IXGRP);
								if (typ & S_IWOTH)
									perm |= who & (S_IWUSR|S_IWGRP);
								if (typ & S_IROTH)
									perm |= who & (S_IRUSR|S_IRGRP);
								if (typ & S_IXGRP)
									perm |= who & S_IXUSR;
								if (typ & S_IWGRP)
									perm |= who & S_IWUSR;
								if (typ & S_IRGRP)
									perm |= who & S_IRUSR;

								/*
								 * if any execute then read => execute
								 */

								if ((typ |= perm) & (S_IXUSR|S_IXGRP|S_IXOTH))
								{
									if (typ & S_IRUSR)
										perm |= who & S_IXUSR;
									if (typ & S_IRGRP)
										perm |= who & S_IXGRP;
									if (typ & S_IROTH)
										perm |= who & S_IXOTH;
								}
								typ = 0;
							}
							break;
						}
						switch (c)
						{
						case '=':
						case '+':
						case '|':
						case '-':
						case '&':
						case '^':
							op = c;
							typ = 0;
							continue;
						}
						if (c)
							break;
						/*FALLTHROUGH*/
					default:
						if (c < '0' || c > '7')
						{
							if (e)
								*e = expr - 1;
							if (typ)
							{
								if (who)
								{
									typ &= who;
									perm &= ~who;
								}
								perm |= typ;
							}
							return perm & S_IPERM;
						}
						num = (num << 3) | (c - '0');
						if (!who && (op == '+' || op == '-'))
							who = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
						if (*expr < '0' || *expr > '7')
						{
							typ |= modei(num);
							num = 0;
						}
						continue;
					}
					break;
				}
				break;
			}
			break;
		}
	}
}