/*
  File: acl_from_text.c

  Copyright (C) 1999, 2000, 2001
  Andreas Gruenbacher, <andreas.gruenbacher@gmail.com>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "config.h"
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include "libacl.h"
#include "misc.h"


#define SKIP_WS(x) do { \
	while (*(x)==' ' || *(x)=='\t' || *(x)=='\n' || *(x)=='\r') \
		(x)++; \
	if (*(x)=='#') { \
		while (*(x)!='\n' && *(x)!='\0') \
			(x)++; \
	} \
	} while (0)


static int parse_acl_entry(const char **text_p, acl_t *acl_p);


/* 23.4.13 */
acl_t
acl_from_text(const char *buf_p)
{
	acl_t acl;
	acl = acl_init(0);
	if (!acl)
		return NULL;
	if (!buf_p) {
		errno = EINVAL;
		return NULL;
	}
	while (*buf_p != '\0') {
		if (parse_acl_entry(&buf_p, &acl) != 0)
			goto fail;
		SKIP_WS(buf_p);
		if (*buf_p == ',') {
			buf_p++;
			SKIP_WS(buf_p);
		}
	}
	if (*buf_p != '\0') {
		errno = EINVAL;
		goto fail;
	}

	return acl;

fail:
	acl_free(acl);
	return NULL;
}


static int
skip_tag_name(const char **text_p, const char *token)
{
	size_t len = strlen(token);
	const char *text = *text_p;

	SKIP_WS(text);
	if (strncmp(text, token, len) == 0) {
		text += len;
		goto delimiter;
	}
	if (*text == *token) {
		text++;
		goto delimiter;
	}
	return 0;

delimiter:
	SKIP_WS(text);
	if (*text == ':')
		text++;
	*text_p = text;
	return 1;
}


static char *
get_token(const char **text_p)
{
	char *token = NULL;
	const char *ep;

	ep = *text_p;
	SKIP_WS(ep);

	while (*ep!='\0' && *ep!='\r' && *ep!='\n' && *ep!=':' && *ep!=',')
		ep++;
	if (ep == *text_p)
		goto after_token;
	token = (char*)malloc(ep - *text_p + 1);
	if (token == 0)
		goto after_token;
	memcpy(token, *text_p, (ep - *text_p));
	token[ep - *text_p] = '\0';
after_token:
	if (*ep == ':')
		ep++;
	*text_p = ep;
	return token;
}


static int
get_id(const char *token, id_t *id_p)
{
	char *ep;
	long l;
	l = strtol(token, &ep, 0);
	if (*ep != '\0')
		return -1;
	if (l < 0) {
		/*
		  Negative values are interpreted as 16-bit numbers,
		  so that id -2 maps to 65534 (nobody/nogroup), etc.
		*/
		l &= 0xFFFF;
	}
	*id_p = l;
	return 0;
}


static int
get_uid(const char *token, uid_t *uid_p)
{
	struct passwd *passwd;

	if (get_id(token, uid_p) == 0)
		return 0;
	errno = 0;
	passwd = getpwnam(token);
	if (passwd) {
		*uid_p = passwd->pw_uid;
		return 0;
	}
	if (errno == 0)
		errno = EINVAL;
	return -1;
}


static int
get_gid(const char *token, gid_t *gid_p)
{
	struct group *group;

	if (get_id(token, (uid_t *)gid_p) == 0)
		return 0;
	errno = 0;
	group = getgrnam(token);
	if (group) {
		*gid_p = group->gr_gid;
		return 0;
	}
	if (errno == 0)
		errno = EINVAL;
	return -1;
}


/*
	Parses the next acl entry in text_p.

	Returns:
		-1 on error, 0 on success.
*/

static int
parse_acl_entry(const char **text_p, acl_t *acl_p)
{
	acl_entry_obj entry_obj;
	acl_entry_t entry_d;
	char *str;
	const char *backup;
	int error, perm_chars;

	new_obj_p_here(acl_entry, &entry_obj);
	init_acl_entry_obj(entry_obj);

	/* parse acl entry type */
	SKIP_WS(*text_p);
	switch (**text_p) {
		case 'u':  /* user */
			if (!skip_tag_name(text_p, "user"))
				goto fail;
			backup = *text_p;
			str = get_token(text_p);
			if (str) {
				entry_obj.etag = ACL_USER;
				error = get_uid(__acl_unquote(str),
						&entry_obj.eid.qid);
				free(str);
				if (error) {
					*text_p = backup;
					return -1;
				}
			} else {
				entry_obj.etag = ACL_USER_OBJ;
			}
			break;

		case 'g':  /* group */
			if (!skip_tag_name(text_p, "group"))
				goto fail;
			backup = *text_p;
			str = get_token(text_p);
			if (str) {
				entry_obj.etag = ACL_GROUP;
				error = get_gid(__acl_unquote(str),
						&entry_obj.eid.qid);
				free(str);
				if (error) {
					*text_p = backup;
					return -1;
				}
			} else {
				entry_obj.etag = ACL_GROUP_OBJ;
			}
			break;

		case 'm':  /* mask */
			if (!skip_tag_name(text_p, "mask"))
				goto fail;
			/* skip empty entry qualifier field (this field may
			   be missing for compatibility with Solaris.) */
			SKIP_WS(*text_p);
			if (**text_p == ':')
				(*text_p)++;
			entry_obj.etag = ACL_MASK;
			break;

		case 'o':  /* other */
			if (!skip_tag_name(text_p, "other"))
				goto fail;
			/* skip empty entry qualifier field (this field may
			   be missing for compatibility with Solaris.) */
			SKIP_WS(*text_p);
			if (**text_p == ':')
				(*text_p)++;
			entry_obj.etag = ACL_OTHER;
			break;

		default:
			goto fail;
	}

	for (perm_chars=0; perm_chars<3; perm_chars++, (*text_p)++) {
		switch(**text_p) {
			case 'r':
				if (entry_obj.eperm.sperm & ACL_READ)
					goto fail;
				entry_obj.eperm.sperm |= ACL_READ;
				break;

			case 'w':
				if (entry_obj.eperm.sperm  & ACL_WRITE)
					goto fail;
				entry_obj.eperm.sperm  |= ACL_WRITE;
				break;

			case 'x':
				if (entry_obj.eperm.sperm  & ACL_EXECUTE)
					goto fail;
				entry_obj.eperm.sperm  |= ACL_EXECUTE;
				break;

			case '-':
				/* ignore */
				break;

			default:
				if (perm_chars == 0)
					goto fail;
				goto create_entry;
		}
	}

create_entry:
	if (acl_create_entry(acl_p, &entry_d) != 0)
		return -1;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress"
	if (acl_copy_entry(entry_d, int2ext(&entry_obj)) != 0)
		return -1;
#pragma GCC diagnostic pop
	return 0;

fail:
	errno = EINVAL;
	return -1;
}

