/*
** Copyright 2003-2004 Double Precision, Inc.
** See COPYING for distribution information.
*/

#include	"maildiraclt.h"
#include	"maildirmisc.h"
#include	"maildircreate.h"
#include	<time.h>
#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif
#if HAVE_DIRENT_H
#include <dirent.h>
#define NAMLEN(dirent) strlen((dirent)->d_name)
#else
#define dirent direct
#define NAMLEN(dirent) (dirent)->d_namlen
#if HAVE_SYS_NDIR_H
#include <sys/ndir.h>
#endif
#if HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#if HAVE_NDIR_H
#include <ndir.h>
#endif
#endif
#include	<string.h>
#include	<errno.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<assert.h>


int maildir_acl_disabled=0;

static int compar_aclt(const void *a, const void *b)
{
	char ca=*(const char *)a;
	char cb=*(const char *)b;

	return (int)(unsigned char)ca - (int)(unsigned char)cb;
}

/* Post-op fixup of an aclt: collate, remove dupes. */

static void fixup(maildir_aclt *aclt)
{
	char *a, *b;

	qsort(*aclt, strlen(*aclt), 1, compar_aclt);

	for (a=b=*aclt; *a; a++)
	{
		if (*a == a[1])
			continue;
		if ((int)(unsigned char)*a <= ' ')
			continue; /* Silently drop bad access rights */

		*b++= *a;
	}
	*b=0;
}

static int validacl(const char *p)
{
	while (*p)
	{
		if ((int)(unsigned char)*p <= ' ')
		{
			errno=EINVAL;
			return -1;
		}
		++p;
	}

	return 0;
}

int maildir_aclt_init(maildir_aclt *aclt,
		      const char *initvalue_cstr,
		      const maildir_aclt *initvalue_cpy)
{
	if (initvalue_cpy)
		initvalue_cstr= *initvalue_cpy;

	*aclt=NULL;

	if (!initvalue_cstr || !*initvalue_cstr)
		return 0;

	if (validacl(initvalue_cstr) < 0)
		return -1;

	if ( (*aclt=strdup(initvalue_cstr)) == NULL)
		return -1;
	fixup(aclt);
	return 0;
}

/* Destroy an aclt after it is no longer used. */

void maildir_aclt_destroy(maildir_aclt *aclt)
{
	if (*aclt)
		free(*aclt);
}


/* Add or remove access chars. */

int maildir_aclt_add(maildir_aclt *aclt,
		     const char *add_strs,
		     const maildir_aclt *add_aclt)
{
	if (add_aclt)
		add_strs= *add_aclt;

	if (!add_strs || !*add_strs)
		return 0;

	if (validacl(add_strs) < 0)
		return -1;

	if (*aclt)
	{
		char *p=realloc(*aclt, strlen(*aclt)+strlen(add_strs)+1);

		if (!p)
			return -1;
		strcat(p, add_strs);
		*aclt=p;

	}
	else if ( ((*aclt)=strdup(add_strs)) == NULL)
		return -1;

	fixup(aclt);
	return 0;
}

int maildir_aclt_del(maildir_aclt *aclt,
		     const char *del_strs,
		     const maildir_aclt *del_aclt)
{
	char *a, *b;

	if (del_aclt)
		del_strs= *del_aclt;

	if (!del_strs)
		return 0;

	if (!*aclt)
		return 0;

	for (a=b=*aclt; *a; a++)
	{
		if (strchr(del_strs, *a))
			continue;
		*b++= *a;
	}
	*b=0;

	if (**aclt == 0)
	{
		free(*aclt);
		*aclt=NULL;
	}
	return 0;
}

/* -------------------------------------------------------------------- */


void maildir_aclt_list_init(maildir_aclt_list *aclt_list)
{
	aclt_list->head=NULL;
	aclt_list->tail=NULL;
}

void maildir_aclt_list_destroy(maildir_aclt_list *aclt_list)
{
	struct maildir_aclt_node *p;

	for (p=aclt_list->head; p; )
	{
		struct maildir_aclt_node *q=p->next;

		free(p->identifier);
		maildir_aclt_destroy(&p->acl);
		free(p);
		p=q;
	}
	maildir_aclt_list_init(aclt_list);
}


/* Add an <identifier,acl> pair.  Returns 0 on success, -1 on failure */

int maildir_aclt_list_add(maildir_aclt_list *aclt_list,
			  const char *identifier,
			  const char *aclt_str,
			  maildir_aclt *aclt_cpy)
{
	struct maildir_aclt_node *p;
	const char *q;

	/* Check for valid identifiers */

	for (q=identifier; *q; q++)
		if ( (int)(unsigned char)*q <= ' ')
		{
			errno=EINVAL;
			return -1;
		}

	if (*identifier == 0)
	{
		errno=EINVAL;
		return -1;
	}

	if (aclt_cpy && *aclt_cpy)
		aclt_str= *aclt_cpy;

	for (p=aclt_list->head; p; p=p->next)
	{
		if (strcmp(p->identifier, identifier) == 0)
		{
			maildir_aclt_destroy(&p->acl);
			return maildir_aclt_init(&p->acl, aclt_str, NULL);
		}
	}

	if ((p=malloc(sizeof(*p))) == NULL ||
	    (p->identifier=strdup(identifier)) == NULL)
	{
		if (p) free(p);
		return -1;
	}

	if (maildir_aclt_init(&p->acl, aclt_str, NULL) < 0)
	{
		free(p->identifier);
		free(p);
		return -1;
	}

	p->next=NULL;
	if ((p->prev=aclt_list->tail) != NULL)
		p->prev->next=p;
	else
		aclt_list->head=p;
	aclt_list->tail=p;
	return 0;
}

/*
** Remove 'identifier' from the ACL list.
*/

int maildir_aclt_list_del(maildir_aclt_list *aclt_list,
			  const char *identifier)
{
	struct maildir_aclt_node *p;

	for (p=aclt_list->head; p; p=p->next)
	{
		if (strcmp(p->identifier, identifier) == 0)
		{
			if (p->prev)
				p->prev->next=p->next;
			else aclt_list->head=p->next;

			if (p->next)
				p->next->prev=p->prev;
			else aclt_list->tail=p->prev;

			maildir_aclt_destroy(&p->acl);
			free(p->identifier);
			free(p);
			return 0;
		}
	}
	return 0;
}

/*
** Generic enumeration.
*/

int maildir_aclt_list_enum(maildir_aclt_list *aclt_list,
			   int (*cb_func)(const char *identifier,
					  const maildir_aclt *acl,
					  void *cb_arg),
			   void *cb_arg)
{
	struct maildir_aclt_node *p;
	int rc;

	for (p=aclt_list->head; p; p=p->next)
	{
		rc= (*cb_func)(p->identifier, &p->acl, cb_arg);

		if (rc)
			return rc;
	}
	return 0;
}

const maildir_aclt *maildir_aclt_list_find(maildir_aclt_list *aclt_list,
					   const char *identifier)
{
	struct maildir_aclt_node *p;

	for (p=aclt_list->head; p; p=p->next)
	{
		if (strcmp(p->identifier, identifier) == 0)
			return &p->acl;
	}
	return NULL;
}

/* ---------------------------------------------------------------------- */

static int maildir_acl_read_check(maildir_aclt_list *aclt_list,
				 const char *maildir,
				 const char *path);

int maildir_acl_read(maildir_aclt_list *aclt_list,
		     const char *maildir,
		     const char *path)
{
	int rc=maildir_acl_read_check(aclt_list, maildir, path);
	char *p, *q;

	if (rc)
		maildir_aclt_list_destroy(aclt_list);

	if (rc <= 0)
		return rc;

	/*
	** If the ACL config file for this folder was not found,
	** check for the ACL config file for its parent folder.
	*/

	if ((p=strdup(path)) == NULL)
		return -1;

	strcpy(p, path);

	q=strrchr(p, '.');

	if (!q)
	{
		free(p);
		errno=EIO; /* Should not happen */
		return -1;
	}

	*q=0;

	rc=maildir_acl_read(aclt_list, maildir, p);
	if (rc == 0)
	{
		/* Make sure to save the default acl list */

		rc=maildir_acl_write(aclt_list, maildir, path, NULL, NULL);
		if (rc >= 0) /* Ok if rc=1 */
			rc=0;
		if (rc)
			maildir_aclt_list_destroy(aclt_list);
	}
	free(p);
	return rc;
}

/*
** Attempt to retrieve the ACL set for the specified folder.
**
** Returns -1 if error.
** Returns 0 if the ACL was retrieved.
** Returns 1 if the ACL configuration file does not exist.
*/

static int maildir_aclt_add_default_admin(maildir_aclt_list *aclt_list);

static int maildir_acl_read_check(maildir_aclt_list *aclt_list,
				  const char *maildir,
				  const char *path)
{
	char *p, *q;
	FILE *fp;
	char buffer[BUFSIZ];

	maildir_aclt_list_init(aclt_list);

	if (!maildir || !*maildir)
		maildir=".";
	if (!path || !*path)
		path=".";

	if (strchr(path, '/') || *path != '.')
	{
		errno=EINVAL;
		return -1;
	}

	if (maildir_acl_disabled)
	{
		if (maildir_aclt_list_add(aclt_list, "owner",
					  ACL_LOOKUP ACL_READ
					  ACL_SEEN ACL_WRITE ACL_INSERT
					  ACL_CREATE
					  ACL_DELETEFOLDER
					  ACL_DELETEMSGS ACL_EXPUNGE,
					  NULL) < 0 ||
		    maildir_aclt_add_default_admin(aclt_list))
		{
			maildir_aclt_list_destroy(aclt_list);
			return -1;
		}
		return 0;
	}

	p=malloc(strlen(maildir)+strlen(path)+2);

	if (!p)
		return -1;

	strcat(strcat(strcpy(p, maildir), "/"), path);

	q=malloc(strlen(p)+sizeof("/" ACLFILE));
	if (!q)
	{
		free(p);
		return -1;
	}
	fp=fopen(strcat(strcpy(q, p), "/" ACLFILE), "r");
	free(p);
	free(q);

	if (fp == NULL)
	{
		if (strcmp(path, ".") == 0)
		{
			/* INBOX ACL default */

			if (maildir_aclt_list_add(aclt_list, "owner",
						  ACL_ALL, NULL) < 0 ||
			    maildir_aclt_add_default_admin(aclt_list))
			{
				return -1;
			}
			return 0;
		}

		q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
			 strlen(path));
		if (!q)
			return -1;

		strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"),
		       path+1);

		fp=fopen(q, "r");
		free(q);
	}

	if (!fp && errno != ENOENT)
		return -1;

	if (!fp)
		return 1;

	errno=0;

	while (fgets(buffer, sizeof(buffer), fp) != NULL)
	{
		char *p=strchr(buffer, '\n');

		if (p) *p=0;

		for (p=buffer; *p; p++)
			if (*p == ' ')
			{
				*p=0;
				do
				{
					++p;
				} while (*p && *p == ' ');
				break;
			}

		if (maildir_aclt_list_add(aclt_list, buffer, p, NULL) < 0)
		{
			if (errno != EINVAL)
				return -1;
			/* Sweep crap in the ACL file under the carpet */
		}
	}
	if (ferror(fp))
	{
		fclose(fp);
		return -1;
	}
	fclose(fp);
	if (maildir_aclt_add_default_admin(aclt_list))
		return -1;
	return 0;
}

/*
** Add the default ACL permissions to the administrators group.
**
** Make sure that the ACL entry for "administrators" includes all
** rights.
**
** Make sure that any ACL entries for "-administrators" or
** "-group=administrators" do not have LOOKUP and ADMIN.
*/

static int maildir_aclt_add_default_admin(maildir_aclt_list *aclt_list)
{
	const maildir_aclt *old_acl;

	static const char * const drop_acls[]={"-administrators",
					       "-group=administrators"};
	size_t i;

	if ((old_acl=maildir_aclt_list_find(aclt_list, "group=administrators"))
	    != NULL)
	{
		maildir_aclt new_acl;

		if (maildir_aclt_init(&new_acl, ACL_ALL, NULL))
			return -1;

		if (maildir_aclt_add(&new_acl, NULL, old_acl) ||
		    maildir_aclt_list_add(aclt_list, "group=administrators",
					  NULL, &new_acl))
		{
			maildir_aclt_destroy(&new_acl);
			return -1;
		}
		maildir_aclt_destroy(&new_acl);
	}
	else
	{
		maildir_aclt new_acl;

		old_acl=maildir_aclt_list_find(aclt_list, "administrators");

		if (maildir_aclt_init(&new_acl, ACL_ALL, NULL))
			return -1;

		if (maildir_aclt_add(&new_acl, NULL, old_acl) ||
		    maildir_aclt_list_add(aclt_list, "administrators",
					  NULL, &new_acl))
		{
			maildir_aclt_destroy(&new_acl);
			return -1;
		}
		maildir_aclt_destroy(&new_acl);
	}

	for (i=0; i<2; i++)
	{
		const char *n=drop_acls[i];
		if (maildir_aclt_list_del(aclt_list, n) < 0)
			return -1;
	}

	return 0;
}

int maildir_acl_delete(const char *maildir,
		       const char *path)
{
	char *p, *q;

#if 0
	if (strcmp(path, SHARED) == 0)
		return 0;

	if (strncmp(path, SHARED ".", sizeof(SHARED)) == 0)
		return 0;
#endif
	if (!maildir || !*maildir)
		maildir=".";
	if (!path || !*path)
		path=".";

	if (strchr(path, '/') || *path != '.')
	{
		errno=EINVAL;
		return -1;
	}

	p=malloc(strlen(maildir)+strlen(path)+2);

	if (!p)
		return -1;

	strcat(strcat(strcpy(p, maildir), "/"), path);

	q=malloc(strlen(p)+sizeof("/" ACLFILE));
	if (!q)
	{
		free(p);
		return -1;
	}

	unlink(strcat(strcpy(q, p), "/" ACLFILE));
	free(p);
	free(q);

	if (strcmp(path, ".") == 0)
	{
		/* INBOX ACL default */

		return 0;
	}

	q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
		 strlen(path));
	if (!q)
	{
		free(p);
		return -1;
	}
	strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"),
	       path+1);

	unlink(q);
	free(q);
	return 0;
}

static int save_acl(const char *identifier, const maildir_aclt *acl,
		    void *cb_arg);


static int is_owner(const char *isme, void *void_arg)
{
	if (void_arg && strcmp(isme, (const char *)void_arg) == 0)
		return 1;

	return strcmp(isme, "owner") == 0;
}

static int is_admin(const char *isme, void *void_arg)
{
	return strcmp(isme, "administrators") == 0;

	/* We don't need to check for group=administrators, see chk_admin() */
}

static int check_adminrights(maildir_aclt *list)
{
	if (strchr(maildir_aclt_ascstr(list), ACL_LOOKUP[0]) == NULL ||
	    strchr(maildir_aclt_ascstr(list), ACL_ADMINISTER[0]) == NULL)
	{
		maildir_aclt_destroy(list);
		return -1;
	}

	maildir_aclt_destroy(list);
	return 0;
}

static int check_allrights(maildir_aclt *list)
{
	const char *all=ACL_ALL;

	while (*all)
	{
		if (strchr(maildir_aclt_ascstr(list), *all) == NULL)
		{
			maildir_aclt_destroy(list);
			return -1;
		}
		++all;
	}

	maildir_aclt_destroy(list);
	return 0;
}

static int maildir_acl_compute_chkowner(maildir_aclt *aclt,
					maildir_aclt_list *aclt_list,
					int (*cb_func)(const char *isme,
						       void *void_arg),
					void *void_arg,
					int chkowner);

int maildir_acl_write(maildir_aclt_list *aclt_list,
		      const char *maildir,
		      const char *path,

		      const char *owner,
		      const char **err_failedrights)
{
	int trycreate;
	struct maildir_tmpcreate_info tci;
	FILE *fp;
	char *p, *q;
	const char *dummy_string;
	maildir_aclt chkacls;

	if (!err_failedrights)
		err_failedrights= &dummy_string;

	if (!maildir || !*maildir)
		maildir=".";
	if (!path || !*path)
		path=".";

	if (strchr(path, '/') || *path != '.')
	{
		errno=EINVAL;
		return -1;
	}

	if (strcmp(path, ".")) /* Sanity check */
		for (dummy_string=path; *dummy_string; dummy_string++)
			if (*dummy_string == '.' &&
			    (dummy_string[1] == '.' ||
			     dummy_string[1] == 0))
			{
				errno=EINVAL;
				return -1;
			}


	if (maildir_acl_compute_chkowner(&chkacls, aclt_list, is_owner, NULL,
					 0))
	{
		maildir_aclt_destroy(&chkacls);
		errno=EINVAL;
		return -1;
	}

	if (check_adminrights(&chkacls))
	{
		*err_failedrights="owner";
		errno=EINVAL;
		return -1;
	}

	if (owner)
	{
		if (maildir_acl_compute_chkowner(&chkacls, aclt_list, is_owner,
						 (void *)owner, 0))
		{
			maildir_aclt_destroy(&chkacls);
			errno=EINVAL;
			return -1;
		}
		if (check_adminrights(&chkacls))
		{
			*err_failedrights=owner;
			errno=EINVAL;
			return -1;
		}
	}

	if (maildir_acl_compute(&chkacls, aclt_list, is_admin, NULL))
	{
		maildir_aclt_destroy(&chkacls);
		errno=EINVAL;
		return -1;
	}
	if (check_allrights(&chkacls))
	{
		errno=EINVAL;
		return -1;
	}

	p=malloc(strlen(maildir)+strlen(path)+2);

	if (!p)
		return -1;

	strcat(strcat(strcpy(p, maildir), "/"), path);

	maildir_tmpcreate_init(&tci);

	tci.maildir=p;
	tci.uniq="acl";
	tci.doordie=1;

	fp=maildir_tmpcreate_fp(&tci);

	trycreate=0;

	if (fp)
	{
		q=malloc(strlen(p) + sizeof("/" ACLFILE));
		if (!q)
		{
			fclose(fp);
			unlink(tci.tmpname);
			maildir_tmpcreate_free(&tci);
			free(p);
			return -1;
		}
		strcat(strcpy(q, p), "/" ACLFILE);
		free(tci.newname);
		tci.newname=q;
		free(p);
	}
	else
	{
		free(p);

		q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
			 strlen(path));
		if (!q)
		{
			maildir_tmpcreate_free(&tci);
			return -1;
		}
		strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"), path+1);

		tci.maildir=maildir;
		tci.uniq="acl";
		tci.doordie=1;

		fp=maildir_tmpcreate_fp(&tci);

		if (!fp)
		{
			free(q);
			maildir_tmpcreate_free(&tci);
			return -1;
		}
		free(tci.newname);
		tci.newname=q;
		trycreate=1;
	}

	if (maildir_aclt_list_enum(aclt_list, save_acl, fp) < 0 ||
	    ferror(fp) || fflush(fp) < 0)
	{
		fclose(fp);
		unlink(tci.tmpname);
		maildir_tmpcreate_free(&tci);
		return -1;
	}
	fclose(fp);

	if (rename(tci.tmpname, tci.newname) < 0)
	{
		/* Perhaps ACLHIERDIR needs to be created? */

		if (!trycreate)
		{
			unlink(tci.tmpname);
			maildir_tmpcreate_free(&tci);
			return -1;
		}

		p=strrchr(tci.newname, '/');
		*p=0;
		mkdir(tci.newname, 0755);
		*p='/';

		if (rename(tci.tmpname, tci.newname) < 0)
		{
			unlink(tci.tmpname);
			maildir_tmpcreate_free(&tci);
			return -1;
		}
	}
	maildir_tmpcreate_free(&tci);
	return 0;
}

static int save_acl(const char *identifier, const maildir_aclt *acl,
		    void *cb_arg)
{
	if (fprintf((FILE *)cb_arg, "%s %s\n",
		    identifier,
		    maildir_aclt_ascstr(acl)) < 0)
		return -1;
	return 0;
}

struct maildir_acl_resetList {
	struct maildir_acl_resetList *next;
	char *mbox;
};

/*
** When a maildir is opened check for stale entries in Maildir/ACLHIERDIR.
**
** Maildir/ACLHIERDIR/folder.subfolder should be removed unless there exists
** Maildir/.folder.subfolder.subsubfolder
**
**
** acl_check_cb is the callback function for maildir_list, which receives
** INBOX.folder.subfolder.subsubfolder.  It goes through the link list with
** Maildir/ACLHIERDIR's contents, and removes folder.subfolder if its found.
**
** After maildir_list is done, anything that's left in the list can be safely
** removed.
*/

static void acl_check_cb(const char *mbox, void *voidarg)
{
	struct maildir_acl_resetList **l=
		(struct maildir_acl_resetList **)voidarg;

	if (strncmp(mbox, INBOX ".", sizeof(INBOX ".")-1))
		return; /* Huh? */

	mbox += sizeof(INBOX ".")-1;

	while (*l)
	{
		int cl= strlen( (*l)->mbox );

		if (strncmp(mbox, (*l)->mbox, cl) == 0 &&
		    mbox[cl] == '.')
		{
			struct maildir_acl_resetList *p= *l;

			*l= p->next;
			free(p->mbox);
			free(p);
			continue;
		}

		l= &(*l)->next;
	}
}

int maildir_acl_reset(const char *maildir)
{
	DIR *dirp;
	struct dirent *de;
	char *p;
	struct maildir_acl_resetList *rl=NULL;
	struct maildir_acl_resetList *r;
	time_t now;
	struct stat stat_buf;

	p=malloc(strlen(maildir) + sizeof("/" ACLHIERDIR));
	if (!p)
		return -1;

	strcat(strcpy(p, maildir), "/" ACLHIERDIR);

	dirp=opendir(p);

	if (!dirp)
	{
		mkdir(p, 0755);
		dirp=opendir(p);
	}
	free(p);

	while (dirp && (de=readdir(dirp)) != NULL)
	{
		if (de->d_name[0] == '.')
			continue;

		if ((r=malloc(sizeof(struct maildir_acl_resetList))) == NULL
		    || (r->mbox=strdup(de->d_name)) == NULL)
		{
			if (r)
				free(r);

			while (rl)
			{
				r=rl;
				rl=r->next;
				free(r->mbox);
				free(r);
			}
			closedir(dirp);
			return -1;
		}

		r->next=rl;
		rl=r;
	}
	if (dirp) closedir(dirp);

	maildir_list(maildir, acl_check_cb, &rl);

	time(&now);

	while (rl)
	{
		r=rl;
		rl=r->next;

		p=malloc(strlen(maildir)+strlen(r->mbox) +
			 sizeof("/" ACLHIERDIR "/"));
		if (p)
		{
			strcat(strcat(strcpy(p, maildir),
				      "/" ACLHIERDIR "/"), r->mbox);

			/* Only unlink stale entries after 1 hour (race) */

			if (stat(p, &stat_buf) == 0 &&
			    stat_buf.st_mtime < now - 60*60)
				unlink(p);
			free(p);
		}
		free(r->mbox);
		free(r);
	}
	return 0;
}

/*
** An ACL entry for "administrators" or "group=administrators" will match
** either one.
*/

static int chk_admin(int (*cb_func)(const char *isme,
				    void *void_arg),
		     const char *identifier,
		     void *void_arg)
{
	if (strcmp(identifier, "administrators") == 0 ||
	    strcmp(identifier, "group=administrators") == 0)
	{
		int rc=(*cb_func)("administrators", void_arg);

		if (rc == 0)
			rc=(*cb_func)("group=administrators", void_arg);
		return rc;
	}

	return (*cb_func)(identifier, void_arg);
}

#define ISIDENT(s) \
	(MAILDIR_ACL_ANYONE(s) ? 1: chk_admin(cb_func, (s), void_arg))

static int maildir_acl_compute_chkowner(maildir_aclt *aclt,
					maildir_aclt_list *aclt_list,
					int (*cb_func)(const char *isme,
						       void *void_arg),
					void *void_arg,
					int chkowner)
{
	struct maildir_aclt_node *p;
	int rc;

	if (maildir_aclt_init(aclt, "", NULL) < 0)
		return -1;

	for (p=aclt_list->head; p; p=p->next)
	{
		if (p->identifier[0] == '-')
			continue;

		rc= ISIDENT(p->identifier);

		if (rc < 0)
		{
			maildir_aclt_destroy(aclt);
			return rc;
		}

		if (rc == 0)
			continue;

		if (maildir_aclt_add(aclt, NULL, &p->acl) < 0)
		{
			maildir_aclt_destroy(aclt);
			return rc;
		}
	}

	for (p=aclt_list->head; p; p=p->next)
	{
		if (p->identifier[0] != '-')
			continue;

		rc= ISIDENT(p->identifier+1);

		if (rc < 0)
		{
			maildir_aclt_destroy(aclt);
			return rc;
		}

		if (rc == 0)
			continue;

		if (maildir_aclt_del(aclt, NULL, &p->acl) < 0)
		{
			maildir_aclt_destroy(aclt);
			return rc;
		}
	}

	/*
	** In our scheme, the owner always gets admin rights.
	*/

	rc=chkowner ? (*cb_func)("owner", void_arg):0;

	if (maildir_acl_disabled)
		rc=0;	/* Except when ACLs are disabled */

	if (rc < 0)
	{
		maildir_aclt_destroy(aclt);
		return rc;
	}

	if (rc)
	{
		if (maildir_aclt_add(aclt, ACL_ADMINISTER, NULL) < 0)
		{
			maildir_aclt_destroy(aclt);
			return rc;
		}
	}
	return 0;
}

int maildir_acl_compute(maildir_aclt *aclt, maildir_aclt_list *aclt_list,
			int (*cb_func)(const char *isme,
				       void *void_arg), void *void_arg)
{
	return maildir_acl_compute_chkowner(aclt, aclt_list, cb_func, void_arg,
					    1);
}

static int chk_array(const char *identifier, void *void_arg);

int maildir_acl_compute_array(maildir_aclt *aclt,
			      maildir_aclt_list *aclt_list,
			      const char * const *identifiers)
{
	return maildir_acl_compute(aclt, aclt_list, chk_array,
				   (void *)identifiers);
}

static int chk_array(const char *identifier, void *void_arg)
{
	const char * const *p=(const char * const *)void_arg;
	size_t i;

	for (i=0; p[i]; i++)
		if (strcmp(identifier, p[i]) == 0)
			return 1;
	return 0;
}

/* -------------------------------------------------------------------- */

int maildir_acl_canlistrights(const char *myrights)
{
	return (strchr(myrights, ACL_LOOKUP[0]) ||
		strchr(myrights, ACL_READ[0]) ||
		strchr(myrights, ACL_INSERT[0]) ||
		strchr(myrights, ACL_CREATE[0]) ||
		strchr(myrights, ACL_DELETEFOLDER[0]) ||
		strchr(myrights, ACL_EXPUNGE[0]) ||
		strchr(myrights, ACL_ADMINISTER[0]));
}

/* --------------------------------------------------------------------- */

/*
** Compute owner ACL identifiers applicable to a mailbox that's owned by
** 'mailbox_owner'.
*/

static int get_owner_list( int (*cb_func)(const char *, void *),
			   const char *c,
			   const char *mailbox_owner, void *arg)
{
	char *a;
	int rc;
	const char *p, *q;

	a=malloc(sizeof("user=")+strlen(c));
	if (!a)
		return -1;

	strcat(strcpy(a, "user="), c);

	rc=(*cb_func)(a, arg);

	if (rc == 0 && strcmp(a, mailbox_owner) == 0)
		rc=(*cb_func)("owner", arg);
	free(a);

	if (rc)
		return rc;

	c=getenv("OPTIONS");

	for (p=c; p && *p; )
	{
		if (*p == ',')
		{
			++p;
			continue;
		}

		q=p;
		while (*p && *p != ',')
			++p;

		if (strncmp(q, "group=", 6) == 0)
		{
			a=malloc(p-q+1);
			if (!a)
				return -1;

			memcpy(a, q, p-q);
			a[p-q]=0;
			rc=(*cb_func)(a, arg);
			free(a);
			if (rc)
				return rc;
		}
	}
	return 0;
}

static int count_owner_list(const char *o, void *arg)
{
	++*(size_t *)arg;

	return 0;
}

static int save_owner_list(const char *o, void *arg)
{
	char ***p=(char ***)arg;

	**p=strdup(o);

	if (**p == NULL)
		return -1;

	++*p;
	return 0;
}

int maildir_acl_computerights(maildir_aclt *rights,
			      maildir_aclt_list *acl_list,
			      const char *me,
			      const char *folder_owner)
{
	char **owner_array;
	size_t owner_cnt;
	char **p;
	int rc;

	owner_cnt=1;

	if (get_owner_list(count_owner_list, me, folder_owner,
			   (void *)&owner_cnt) ||
	    (owner_array=calloc(owner_cnt, sizeof(char *))) == NULL)
		return -1;

	p=owner_array;

	if (get_owner_list(save_owner_list, me, folder_owner, (void *)&p))
	{
		for (owner_cnt=0; owner_array[owner_cnt]; ++owner_cnt)
			free(owner_array[owner_cnt]);
		free(owner_array);
		return -1;
	}

	rc=maildir_acl_compute_array(rights, acl_list,
				     (const char * const *)owner_array);

	for (owner_cnt=0; owner_array[owner_cnt]; ++owner_cnt)
		free(owner_array[owner_cnt]);
	free(owner_array);
	return rc;
}
